'Passing data form parent to Child with the value fetch form API

I wanna passing the selectedCategory (it is State hook) to the Child Item, First of all, I use the getServiceCatoriesAsync API (redux toolkit) and pass props.serviceCategories[0]?._id to State to fetch initialState (ID of Category).

In Child Component, I receive selectedCategory with the value: undefined

How to fix this.

    const ServicesScreen = (props) => {
        //! props: navigation, route,
        //! props Redux: serviceCategories, getServiceCategoriesAsync
    
        const nCount = React.useRef(0);
        console.log(`ServicesScreen - render `, (nCount.current += 1));
    
        const [selectedCategory, setSelectedCategory] = React.useState(props.serviceCategories[0]?._id);
    
        React.useEffect(() => {
            let isSubscribed = true;
            if (isSubscribed) {
                props.getServiceCategoriesAsync();
            }
            return () => {
                isSubscribed = false; //! Cancel the subscription
            };
        }, [selectedCategory]);

    return (
        <View style={styles.container}>
            <PanelServiceCategory
                theme={theme}
                style={styles.containerPanelCategory}
                setSelectedCategory={setSelectedCategory}
                selectedCategory={selectedCategory}
                serviceCategories={props.serviceCategories}
                {...props}
            />
            <PanelServices style={styles.containerPanelService} />
        </View>
    );
};

servicesSlice

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { PlatformBaseUrl } from '../../../utils';

//! GET ServiceCategory
export const getServiceCategoriesAsync = createAsyncThunk('services/getServiceCategoriesAsync', async () => {
    const response = await fetch(PlatformBaseUrl.baseApiUrl('/api/services'));
    if (response.ok) {
        const { serviceCategories } = await response.json();
        return serviceCategories; // payload Action
    }
});

//! CREATE ServiceCategory
export const addServiceCategoryAsync = createAsyncThunk('services/addServiceCategoryAsync', async (payload) => {
    const response = await fetch(PlatformBaseUrl.baseApiUrl('/api/services'), {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ name: payload.name }),
    });

    if (response.ok) {
        const { serviceCategory } = await response.json();
        return serviceCategory; //! return Action 1 Array
    }
});

//! CREATE Service
export const addServiceAsync = createAsyncThunk('services/addServiceAsync', async (payload, { getState }) => {
    const { serviceCategoryId } = getState().modal.modalProps; //! OK
    
    const response = await fetch(PlatformBaseUrl.baseApiUrl(`/api/services/${serviceCategoryId}`), {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ name: payload.name, price: payload.price, description: payload.description }),
    });

    if (response.ok) {
        const { service } = await response.json();
        return service;
    }
});

//! DELETE Service
export const removeServiceAsync = createAsyncThunk('services/removeServiceAsync', async (payload, { getState }) => {
    const { serviceCategoryId, serviceId } = getState().modal.modalProps;

    const response = await fetch(PlatformBaseUrl.baseApiUrl(`/api/services/${serviceCategoryId}/${serviceId}`), {
        method: 'DELETE',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ serviceId }),
    });

    if (response.ok) {
        const { service } = await response.json();
        return service;
    }
});

//! UPDATE Service
export const updateServiceAsync = createAsyncThunk('services/updateServiceAsync', async (payload, { getState }) => {
    const { serviceCategoryId, serviceId } = getState().modal.modalProps;
    const { name, price, description } = payload;

    const response = await fetch(PlatformBaseUrl.baseApiUrl(`/api/services/${serviceCategoryId}/${serviceId}`), {
        method: 'PATCH',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ name, price, description }),
    });

    if (response.ok) {
        const { updatedService } = await response.json();
        return updatedService; //! return a Object
    }
});

const initialState = {
    isLoading: false,
    error: false,
    serviceCategories: [],
};

const servicesSlice = createSlice({
    name: 'services',
    initialState,
    reducers: {},
    extraReducers: (builder) => {
        builder.addCase(getServiceCategoriesAsync.pending, (state, action) => {
            console.log('getServiceCategoriesAsync pending');
        });
        builder.addCase(getServiceCategoriesAsync.fulfilled, (state, action) => {
            console.log('getServiceCategoriesAsync fulfilled');
            state.serviceCategories = action.payload;
        });
        builder.addCase(addServiceCategoryAsync.fulfilled, (state, action) => {
            console.log('addServiceCategoryAsync fulfilled');
            state.serviceCategories.push(action.payload);
        });
        builder.addCase(addServiceAsync.pending, (state, action) => {
            console.log('addServiceAsync pending');
        });
        builder.addCase(addServiceAsync.fulfilled, (state, action) => {
            console.log('addServiceAsync fulfilled');
            let categories = [...state.serviceCategories];
            let catIndex = categories.findIndex((item) => item._id === action.payload.category);
            if (catIndex != -1) categories[catIndex].services.push(action.payload);
            state.serviceCategories = categories;
        });
        builder.addCase(removeServiceAsync.pending, (state, action) => {
            console.log('removeServiceAsync pending');
        });
        builder.addCase(removeServiceAsync.fulfilled, (state, action) => {
            console.log('removeServiceAsync fulfilled');
            let categories = state.serviceCategories;
            let catIndex = categories.findIndex((item) => item._id === action.payload.category);
            let updatedServices = categories[catIndex].services.filter((service) => service._id !== action.payload._id);
            if (catIndex != -1) state.serviceCategories[catIndex].services = updatedServices;
        });
        builder.addCase(updateServiceAsync.pending, (state, action) => {
            console.log('updateServiceAsync pending');
        });
        builder.addCase(updateServiceAsync.fulfilled, (state, action) => {
            console.log('updateServiceAsync fulfilled');
            let categories = state.serviceCategories;
            let catIndex = categories.findIndex((item) => item._id === action.payload.category);
            let updatedServices = categories[catIndex].services.map((service) => (service._id === action.payload._id ? action.payload : service));
            if (catIndex != -1) state.serviceCategories[catIndex].services = updatedServices;
        });
    },
});

//! exp Actions
export const {} = servicesSlice.actions;
//! exp Reducer
export default servicesSlice.reducer;


Solution 1:[1]

I wish I could comment this under your post but my rep is too low so oh well.

The problem may be caused due to several reasons. To debug you need to show the parent file which gives props.serviceCategories[0]?._id to ServicesScreen. And also show how it calls the redux store to gain access to said data.

Also show the slice that handles the state for serviceCategories. It might be the case that you are mutating the state and hence the store is not causing a re-render.

EDIT

Alright so basically you are mutating some states that Immer cannot handle in redux-toolkit. the cases are wherever this has been done:

state.serviceCategories[catIndex].services = updatedServices;

According to the docs arrays are mutable in nature and changing them in such fashion means Immer cannot apply a copy to the state change (Although it is able to do so inside the createReducer() method). Therefore a better approach would be:

// inside updateServiceAsync.fulfilled and removeServiceAsync.fulfilled

let elementForInsertion = {...state.serviceCategories[catIndex], services: updatedServices}
if (catIndex != -1) state.seviceCategories = [...state.serviceCategories.slice(0,catIndex), elementForInsertion, ...state.serviceCategories.slice(catIndex+1)]

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1