Blog>
Snippets

Fetching Data with useReducer

Demonstrate how to fetch data from an API, and manage loading, success, and error states using useReducer for a more predictable state transition.
import React, { useReducer, useEffect } from 'react';

// Define action types
const actionTypes = {
  FETCH_INIT: 'FETCH_INIT',
  FETCH_SUCCESS: 'FETCH_SUCCESS',
  FETCH_FAILURE: 'FETCH_FAILURE'
};

// Define the initial state
const initialState = {
  isLoading: false,
  isError: false,
  data: []
};

// Reducer function to handle state transitions based on action types
function dataFetchReducer(state, action) {
  switch (action.type) {
    case actionTypes.FETCH_INIT:
      return { ...state, isLoading: true, isError: false };
    case actionTypes.FETCH_SUCCESS:
      return { ...state, isLoading: false, isError: false, data: action.payload };
    case actionTypes.FETCH_FAILURE:
      return { ...state, isLoading: false, isError: true };
    default:
      throw new Error();
  }
}
This piece of code sets up the necessary imports, action types, initial state, and a reducer function to handle different states such as loading, success, and failure during a data fetch operation.
const MyComponent = ({ apiUrl }) => {
  const [state, dispatch] = useReducer(dataFetchReducer, initialState);

  useEffect(() => {
    let didCancel = false;

    const fetchData = async () => {
      dispatch({ type: actionTypes.FETCH_INIT });

      try {
        const response = await fetch(apiUrl);
        const result = await response.json();

        if (!didCancel) {
          dispatch({ type: actionTypes.FETCH_SUCCESS, payload: result });
        }
      } catch (error) {
        if (!didCancel) {
          dispatch({ type: actionTypes.FETCH_FAILURE });
        }
      }
    };

    fetchData();

    // Cleanup function to avoid updating state after a component unmounts
    return () => {
      didCancel = true;
    };
  }, [apiUrl]);

  return (
    <div>
      {state.isError && <div>Something went wrong...</div>}
      {state.isLoading ? <div>Loading...</div> : <ul>{state.data.map(item => <li key={item.id}>{item.title}</li>)}</ul>}
    </div>
  );
};

export default MyComponent;
This code defines a React component that uses the useReducer hook with the defined reducer function and the initial state. It also includes a side effect to fetch data when the component mounts or the apiUrl prop changes, and it handles the cleanup to avoid state updates after the component unmounts. The component renders different UI states based on whether it's loading, has an error, or has successfully fetched data.