0

I need your help. I am using react and redux along with typescript in my app. Using the code below, I get my products and their categories. The fact is that I get my products and they look great, but the categories don't work very well: sometimes i have to reload the page several times for them to appear, and sometimes i don't. Tell me how to fix it? Thank you very much

ApiService

export const getAllCategories = () => {
    return async (dispatch: Dispatch<ProductActionModel>, getState: () => RootState) => {
        try {
            dispatch({ type: Action_types.GET_ALL_CATEGORIES });
            const response = await fetch(`${environment.baseApiUrl}/products/categories`).then(res => res.json());
            const state = getState();
            dispatch({ type: Action_types.GET_ALL_CATEGORIES_SUCCESS, payload: [...state.products.categories, ...response] });
        } catch (error) {
            dispatch({ type: Action_types.GET_ALL_CATEGORIES_ERROR, payload: 'Something went wrong'})
        }
    }
}


export const getAllProducts = () => {
    return async (dispatch: Dispatch<ProductActionModel>) => {
        try {
            dispatch({ type: Action_types.GET_ALL_PRODUCTS });
            const response = await fetch(`${environment.baseApiUrl}/products`).then(response => response.json())
            dispatch({ type: Action_types.GET_PRODUCTS_SUCCESS, payload: response.products })
        } catch (e) {
            dispatch({ type: Action_types.GET_PRODUCTS_ERROR, payload: 'Something went wrong' });
        }
    }
}

ProductReducer

const initialState: ProductsStateModel = {
    products: [],
    categories: [],
    loading: false,
    error: null
}

export const ProductReducer = (state = initialState, action: ProductActionModel): ProductsStateModel => {
    switch (action.type) {

        case Action_types.GET_ALL_PRODUCTS:
            return { loading: true, error: null, products: [], categories: [] }
        case Action_types.GET_PRODUCTS_ERROR:
            return { loading: false, error: action.payload, products: [], categories: [] }
        case Action_types.GET_PRODUCTS_SUCCESS:
            return { loading: false, error: null, products: action.payload, categories: [] }
        case Action_types.GET_ALL_CATEGORIES:
            return state;
        case Action_types.GET_ALL_CATEGORIES_ERROR:
            return state;
        case Action_types.GET_ALL_CATEGORIES_SUCCESS:
            return { ...state, categories: [...state.categories, ...action.payload] }
        default:
            return state;
    }
}

ProductList

const { products, error, loading, categories } = useTypesSelector(state => state.products);
    const dispatch: ThunkDispatch<RootState, void, ProductActionModel> = useDispatch();

    useEffect(() => {
        dispatch(getAllProducts());
        dispatch(getAllCategories())
    },[dispatch])

    console.log(categories); // sometimes fine, sometimes [], [], [categories], []
  • If you put only dispatch(getAllCategories()) inside useEffect in ProductList, how does it log? I'm assuming those getAllProducts() and getAllCategories() race and update randomly your state. I can't simulate your code now, so this is not case, but try chaining with thenable with dispatch or unify getAllProducts and getAllCategories into one reducer. – iamippei Mar 24 '23 at 13:37
  • @iamippei If I leave the categories in useEffect, I get everything showing up fine, but no products. Could you please show what you have in store? Thank you very much – Super World Mar 24 '23 at 13:45

3 Answers3

0

It seems you want to execute reducers in order. Can you do chaining like following?

const { products, error, loading, categories } = useTypesSelector(state => state.products);
const dispatch: ThunkDispatch<RootState, void, ProductActionModel> = useDispatch();

useEffect(() => {
    dispatch(getAllProducts()).then(()=>dispatch(getAllCategories()));
},[dispatch])

console.log(categories); // sometimes fine, sometimes [], [], [categories], []

Or, make one reducer containing getAllProducts() and getAllCategories().

export const getAllCategories = () => {
return async (dispatch: Dispatch<ProductActionModel>, getState: () => RootState) => {
    try {
        dispatch({ type: Action_types.GET_ALL_CATEGORIES });
        const response = await fetch(`${environment.baseApiUrl}/products/categories`).then(res => res.json());
        const state = getState();
        dispatch({ type: Action_types.GET_ALL_CATEGORIES_SUCCESS, payload: [...state.products.categories, ...response] });
    } catch (error) {
        dispatch({ type: Action_types.GET_ALL_CATEGORIES_ERROR, payload: 'Something went wrong'})
    }
}}

export const getAllProducts = () => {
return async (dispatch: Dispatch<ProductActionModel>) => {
    try {
        dispatch({ type: Action_types.GET_ALL_PRODUCTS });
        const response = await fetch(`${environment.baseApiUrl}/products`).then(response => response.json())
        dispatch({ type: Action_types.GET_PRODUCTS_SUCCESS, payload: response.products })
    } catch (e) {
        dispatch({ type: Action_types.GET_PRODUCTS_ERROR, payload: 'Something went wrong' });
    }
}}

export const initialLoader= () => {
return async (dispatch: Dispatch<ProductActionModel>, getState: () => RootState) => { // Unify above functions}}
iamippei
  • 148
  • 9
0

Seems like you are having to deal with a lot of states. I suggest using React Query. The useQueries hook can be used to fetch a variable number of requests.

const results = useQueries([
  { queryKey: ['products'], queryFn: yourProductsFetchFunction},
  { queryKey: ['categories'], queryFn: yourCategoriesFetchFunction },
])

As the doc says, TanstackQuery provides you really helpfull states, in which you can know if your data was succesfully or not settled, that is going help you deal with your problem. To get the loading status of the first Query, you can use results[0].isLoading. Some of the states are:

isError or status === 'error' - The query encountered an error
isSuccess or status === 'success' - The query was successful and data is available
isIdle or status === 'idle' - The query is currently disabled (you'll learn more about this in a bit)

Joao Victor
  • 1
  • 1
  • 3
  • TS2322: Type '(dispatch: Dispatch<ProductActionModel>) => Promise<void>' is not assignable to type 'QueryFunction<unknown, QueryKey>'.
    Types of parameters 'dispatch' and 'context' are incompatible.
    Type 'QueryFunctionContext<QueryKey, any>' is not assignable to type 'Dispatch<ProductActionModel>'.
    Type 'QueryFunctionContext<QueryKey, any>' provides no match for the signature '<T extends ProductActionModel>(action: T): T'.
    – Super World Mar 24 '23 at 14:22
  • Provide `queryFn` a simple function returning fetch request, e.g. `() => api.get(URL)` – Joao Victor Mar 24 '23 at 14:28
0

As i can understand from the code,

console.log(categories); // sometimes fine, sometimes [], [], [categories], []

Component will re-render every time state update(here i mean redux store).

When component mount,Initially your categories are empty say [],(first [] in console.log) then component re-render when you load products(second [] in console.log),

finally you update state when actually categories loaded(thats when you get [categories] in console.log)

Now you should have indicator in your store that indicates your api is loading the categories,and you should not process categories until its being loaded from the api.

I can see , there is loading flag in the state but its only used for products loading indication. Hope this will help you.

Jatin Parmar
  • 2,759
  • 5
  • 20
  • 31