javascriptreactjsreact-hooksreact-contextuse-reducer

How to set useReducer initial state using API response?


I'm new to React and trying to build an app with a cart feature where users can add robots to the cart. I'm using Context API to provide cart across the app and useReducer for states. The robots are being fetched from a server and load just fine. But I can't seem to find a way to set the initial state of the reducer using fetched products. The state always returns an empty array for robots. How to solve this?

const CartProvider = ({ children }) => {
    const [robots, setRobots] = useState([]);

    useEffect(() => {
        fetch('http://localhost:8000/api/robots')
            .then(res => res.json())
            .then(data => setRobots(data?.data))
    }, [])

    const initialState = {
        robots: [...robots],
        cart: []
    }
    const [state, dispatch] = useReducer(cartReducer, initialState);

    return (
        <CartContext.Provider value={{ state, dispatch }}>
            {children}
        </CartContext.Provider>
    );
}
export default CartProvider;

Solution

  • Since the initial state value is fetched asynchronously you'll need to dispatch an action to set the state value.

    Example:

    const cartReducer = (state, action) => {
      switch(action.type) {
        ... other reducer cases ...
    
        case 'INITIALIZE_CART': 
          return action.payload;
    
        default:
          return state;
      }
    };
    

    CartProvider

    const initialState = {
      robots: [],
      cart: []
    };
    
    const CartProvider = ({ children }) => {
      const [robots, setRobots] = useState([]);
    
      const [state, dispatch] = useReducer(cartReducer, initialState);
    
      useEffect(() => {
        fetch('http://localhost:8000/api/robots')
          .then(res => res.json())
          .then(data => {
            dispatch({
              type: 'INITIALIZE_CART',
              payload: {
                ...initialState,
                robots: data?.data,
              }
            });
          });
      }, []);
    
      return (
        <CartContext.Provider value={{ state, dispatch }}>
          {children}
        </CartContext.Provider>
      );
    };
    

    It's common to also hold some state on the app initialization status. UI conditionally renders based on this state value.

    Example:

    const initialState = {
      initialized: false,
      robots: [],
      cart: []
    };
    

    ...

    const cartReducer = (state, action) => {
      switch(action.type) {
        ... other reducer cases ...
    
        case 'INITIALIZE_CART': 
          return {
            ...action.payload,
            initialized: true,
          };
    
        default:
          return state;
      }
    };