Tarsh
Tarsh

Reputation: 71

How to return state in reducer function during API call

I have a reducer function in which I have to make an API call and then return its result as state. But because of the API call being asynchronous the value of state is not updated.

if (action.type==='CREATE_POST') {
        const x= async () => {
            const res=await  fetch("http://localhost:5000/posts", {
                  method: "post",
                  headers: {
                      'Accept': 'application/json',
                      'Content-Type': 'application/json'
                  },
                  body: JSON.stringify(
                      action.payload
                  )
              })
                  const data= await res.json()
              return data
          }
          return [...state,x]
}

I have also tried this

if (action.type==='CREATE_POST') {
        const x= async () => {
            const res=await  fetch("http://localhost:5000/posts", {
                  method: "post",
                  headers: {
                      'Accept': 'application/json',
                      'Content-Type': 'application/json'
                  },
                  body: JSON.stringify(
                      action.payload
                  )
              })
                  const data= await res.json()
              return data
          }
          return [...state,x().then((data)=>{console.log(data);})]

    }

Upvotes: 4

Views: 384

Answers (2)

Rahul Sharma
Rahul Sharma

Reputation: 10081

Don't call API from the reducer, reducers are pure functions and it'll not wait for API call to finish and update state.

The best way to create another function/hook separated from the component and do all these operations there, so that your component will be clean.

I use the following approach to solve this, I create a custom hook for each service like useUserDispach, useAuthDispatch, etc. to keep all these logic away from components.

You can create a custom dispatcher for each page/module/service etc depending on your use case.

custom hook

 const usePostDispatch = () => {
  const dispatch = useDispatch();
  const getPosts = async () => {
    try {
      dispatch({ type: "FETCH_REQUEST" });
      const res = await fetch("http://localhost:5000/posts", {
        method: "post",
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
        },
        body: JSON.stringify(action.payload),
      });
      const data = await res.json();
      dispatch({ type: "FETCH_SUCCESS", payload: data });
      return data;
    } catch (error) {
      dispatch({ type: "FETCH_FAILURE", payload: error });
    }
  };
  return {
    getPosts,
  };
};

Reducer

const initialState = {
  loading: false,
  data: [],
  error: "",
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "FETCH_REQUEST":
      return {
        ...state,
        loading: true,
      };
    case "FETCH_SUCCESS":
      return {
        loading: false,
        data: action.payload,
        error: "",
      };
    case "FETCH_FAILURE":
      return {
        loading: false,
        data: [],
        error: action.payload,
      };
    default:
      return state;
  }
};

export default reducer;

Component

const { getPosts } = usePostDispatch(); // Make sure you call this at top level
useEffect(() => {
  getPosts();
}, []);

Upvotes: 1

Aryaman Kumar
Aryaman Kumar

Reputation: 68

If you are doing an asynchronous task, I suggest you this pattern:

Create 3 files , named:

  1. reducer.js
  2. action.js
  3. effect.js (for asynchronous task)

In reducer

const initialState = {
  loading: false,
  data: [],
  error: ''
}

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "FETCH_REQUEST":
      return {
        ...state,
        loading: true
      }
    case "FETCH_SUCCESS":
      return {
        loading: false,
        data: action.payload,
        error: ''
      }
    case "FETCH_FAILURE":
      return {
        loading: false,
        data: [],
        error: action.payload
      }
    default: return state
  }
}

export default reducer

In action.js

export const fetchRequest = () => {
    return {
      type: "FETCH_REQUEST"
    }
  }
  
  export const fetchSuccess = data => {
    return {
      type: "FETCH_SUCCESS",
      payload: data
    }
  }
  
  export const fetchFailure = error => {
    return {
      type: "FETCH_FAILURE",
      payload: error
    }
  }

Finally the effect.js

export const fetch = () => {
    return (dispatch) => {
        //Initializing the request
        dispatch(fetchRequest());
        //Calling the api
        api()
            .then((response) => {
                // response is the data
                dispatch(fetchSuccess(response));
            })
            .catch((error) => {
                // error.message is the error message
                dispatch(fetchFailure(error.message));
            });
    };
};

Upvotes: 2

Related Questions