Reputation: 71
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
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
Reputation: 68
If you are doing an asynchronous task, I suggest you this pattern:
Create 3 files , named:
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