How to clean up redux state with redux thunk when unmounting component before finishing fetch in useEffect? - reactjs

After some time working with react-redux and redux thunk I have realise about a behaviour, which isnt the best user experience.
I know that when you are working with react and you are fetching data in useEffect when the component is rendering and for any reason you go back or navigate somewhere else you need to clear the state with a function in the return (which will recreate the componentWillUnmount lifecycle)
This problem I am facing however occurs when working with redux thunk because the data fetch is with the actions creators. So to make my long story short I will show some code. The fetching action looks something like this:
export const fetchData = () => async (dispatch, getState) => {
try {
dispatch(fetchDataStart())
const response = await fetch('https://jsonplaceholder.typicode.com/photos')
dispatch(fetchDataSucess(data))
} catch (error) {
console.log(error)
}
}
Let's say that I call this action in my component useEffect like this:
useEffect(() => {
const loadData = async () => {
await dispatch(fetchData())
}
loadData()
return () => dispatch(resetData())
},[ ])
As you can see I am dispatching a resetData action to clear the state when the component unmounts BUUUUT this is where the problem arrives. If before fetching the data the user navigates to another page, the resetData will be dispatched BUT as the fetch could not be finished the data will be stored after having been reseted. So when the user navigates back to that component it will blink (show only very quickly, maybe for a second) the old data before loading the new one. So is there any way to avoid this problem with redux thunk?
PD: I could block the navigation or the whole screen with a backdrop so the user wont navigate until the fetch is finished but i feel like that is kind of a workaround of the problem. However, let me know if you think that this would still be the best way.
Thank you.

You can create an AbortController instance. That instance has a signal property, and we pass the signal as a fetch option. Then to cancel data fetching we call the AbortController's abort property to cancel all fetches that use that signal.
export const fetchData = () => async (dispatch) => {
try {
const controller = new AbortController();
const { signal } = controller;
dispatch(fetchDataStart(controller)); // save it to state to call it later
const response = await fetch('https://jsonplaceholder.typicode.com/photos', { signal });
dispatch(fetchDataSucess(data));
} catch (error) {
console.log(error);
}
}
useEffect:
useEffect(() => {
dispatch(fetchData());
return () => dispatch(resetData()) // resetData will call controller.abort() that was saved in state
},[ ])

Related

ReactJS delay update in useState from axios response

I am new to react js and I am having a hard time figuring out how to prevent delay updating of use state from axios response
Here's my code:
First, I declared countUsername as useState
const [countUsername, setUsername] = useState(0);
Second, I created arrow function checking if the username is still available
const checkUser = () => {
RestaurantDataService.checkUsername(user.username)
.then(response => {
setUsername(response.data.length);
})
.catch(e => {
console.log(e);
})
}
So, every time I check the value of countUsername, it has delay like if I trigger the button and run checkUser(), the latest response.data.length won't save.
Scenario if I console.log() countUseranme
I entered username1(not available), the value of countUsername is still 0 because it has default value of 0 then when I trigger the function once again, then that will just be the time that the value will be replaced.
const saveUser = () => {
checkUser();
console.log(countUsername);
}
Is there anything that I have forgot to consider? Thank you
usually there is a delay for every api call, so for that you can consider an state like below:
const [loading,toggleLoading] = useState(false)
beside that you can change arrow function to be async like below:
const checking = async ()=>{
toggleLoading(true);
const res = await RestaurantDataService.checkUsername(user.username);
setUsername(response.data.length);
toggleLoading(false);
}
in the above function you can toggle loading state for spceifing checking state and disable button during that or shwoing spinner in it:
<button onClick={checking } disabled={loading}>Go
i hope this help
.then is not synchronous, it's more of a callback and will get called later when the api finishes. So your console log actually goes first most of the time before the state actually saves. That's not really something you control.
You can do an async / await and return the data if you need to use it right away before the state changes. And I believe the way state works is that it happens after the execution:
"State Updates May Be Asynchronous" so you can't really control when to use it because you can't make it wait.
In my experience you use the data right away from the service and update the state or create a useEffect, i.g., useEffect(() => {}, [user]), to update the page with state.
const checkUser = async () => {
try {
return await RestaurantDataService.checkUsername(user.username);
} catch(e) {
console.log(e);
}
}
const saveUser = async () => {
const user = await checkUser();
// do whatever you want with user
console.log(user);
}

useEffect infinite loop with redux toolkit dispatch and fetch

I'm using redux-toolkit in react, i have a loading state for the entire app. In a component I'm doing a function to dispatch the loading to true then fetch data then dispatch loading to false. Then putting that function in a useEffect. Here's the code:
const getUsers = async () => {
try {
dispatch(setLoading(true));
const req = await axios.get(URL);
const res = req.data;
setUsers(res);
dispatch(setLoading(false));
console.log("data fetched");
} catch (err) {
console.log(err);
}
};
useEffect(() => {
getUsers();
}, []);
The problem is that the useEffect is running infinitely non-stop.
If i remove the 2nd dispatch in the function(dispatch(setLoading(false))), it works and useEffect runs once. or even if i keep the 1st dispatch and keep the 2nd it also works.
If i remove the data fetching and just keep the two dispatch, it works.
why is this happening? at first i thought it's because running two dispatch but then it seems related to combining fetching with these. I do this in basic redux and never faced a problem like this one.
Turns out the problem isn't with redux-toolkit but a mistake i've made with react-router v6.

Update state in setInterval via dispatch outside component

I currently have a functional component Form that triggers a task to occur. Once the submission is complete, I create a setInterval poll to poll for the status of the task. The code roughly looks like
export function Form(props: FormProps) {
const dispatch = useDispatch()
const pollTaskStatus = () => {
const intervalId = setInterval(async() => {
const response = await fetchTaskStatus() // Function in different file
if (response.status === 'COMPLETE') {
dispatch(Actions.displayTaskComplete())
clearInterval(intervalId)
}
})
}
const submitForm = async() => {
await onSubmitForm() // Function in different file
pollTaskStatus()
}
return (
...
<button onClick={submitForm}>Submit</button>
)
}
When the action is dispatched, the redux store is supposed to be updated and a component is supposed to update alongside it showing a message that the task is complete. However, I see the action logged with an updated store state but nothing occurs. If I just try to dispatch the same action with useEffect() wrapped around it outside the submitForm functions, the message appears. I've searched online and people say that you need to wrap useEffect around setInterval but I can't do that because the function that calls setInterval is not a custom hook or component. Is there a way to do this?
It's a bit difficult to answer your question without seeing all the code.
But my guts feeling is that this might no have anything to do with React.
const pollTaskStatus = () => {
const intervalId = setInterval(async() => {
console.log('fire to fetch')
const response = await fetchTaskStatus() // Function in different file
if (response.status === 'COMPLETE') {
console.log('success from fetch')
dispatch(Actions.displayTaskComplete())
}
})
}
Let's add two console lines to your code. What we want to see is to answer the following questions.
is the setInterval called in every 500ms?
is any of the call finished as success?
how many dispatch has been fired after submission
If you can answer all these questions, most likely you can figure out what went wrong.

Ho to wait with fetch until redux state is available?

I want to fetch some data from a database, and depending on the user the returned data should differ. The way i tried it, was by passing the userid as a query. The id is stored as a redux state. The problem is, that it takes some time before the redux state is available. Ive tried fixing this with if statements, and rerunning the useEffect everytime the auth state is updated. This doesn't work.
I want to fetch, when the redux state auth.user.id is available. Which it is like .1 sec after the initial load.
Here is my code:
const auth = useSelector((state) => state.auth);
useEffect(async () => {
if (auth.token.length > 0) {
const res = await getData(`calendar/?userId=${auth.user.id}`);
setLessons(res.lessons);
setEvents(res.events);
}
}, [auth, date]);
I believe useEffect is already asynchronous, so you don't need to use the async keyword in the anonymous callback. You can create the async function for that logic elsewhere and call it within the useEffect.
Similarly, you could put in self calling async function within your useEffect as such:
useEffect(() => {
(async () => {
if (auth.token.length) {
try {
const res = await getData(`calendar/?userId=${auth.user.id}`);
setLessons(res.lessons);
setEvents(res.events);
}catch (err) {console.log(err);}
}
})();
}, [auth, date]);
I think this link may be helpful:
React Hook Warnings for async function in useEffect: useEffect function must return a cleanup function or nothing
So with the basic understanding, I assume that you need to call the API whenever userId is available. try the below useEffect
useEffect(async () => {
// check user id is available here
if (auth.user && auth.user.id) {
const res = await getData(`calendar/?userId=${auth.user.id}`);
setLessons(res.lessons);
setEvents(res.events);
// some other statements
}
}, [auth, date]);

Not waiting to get result from useDispatch() in react redux

I have stuck with some issue while using hooks with redux using useDispatch, useSelector. same issue with mapDispatchToProps, mapStateToProps.
I have attached sample code below.based on that the issue is..
On button click(saveSettings() method) when i tried to call an action method using dispatch(cn_fetchData()) after getting the success result from another dispatch(cn_saveData()) call, it will not wait to get the success message using useSelector "cn_saveDataValue ". Tried to use async await. but it failed.
const dispatch = useDispatch();
const cn_fetchData = (appId) => dispatch(_fetchcn_appValues(appId));
const cn_saveData = (appId, testData) => dispatch(_saveSettings(appId, testData));
const cn_saveDataValue = useSelector(state => state.AppReducer.cn_saveDataValue);
const cn_appValues = useSelector(state => state.AppReducer.appData);
const saveSettings = async () => {
try {
await cn_saveData(
appData._id,
testData,
);
// **Issue is here. Its not waiting to get cn_saveDataValue to true after updating to the database using "await cn_saveData"**
if (cn_saveDataValue) {
await cn_fetchData(appData._id); // Fetch updated data from the database using action, reducer
}
} catch {
console.log("Err !! Please Try again !");
}
}
I am using action and reducer. Set response from reducer. if (cn_saveDataValue) getting false first time when save settings. so await cn_fetchData(appData._id); method not firing.
Because cn_saveData is a sync function, not an async fn so it can not wait to get cn_saveDataValue to true after updating
You should use useEffect in this case:
useEffect(() => {
if (cn_saveDataValue) {
cn_fetchData(appData._id);
}
}
}, [cn_saveDataValue]);

Resources