useEffect infinite loop with redux toolkit dispatch and fetch - reactjs

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.

Related

how to know in react useEffect, if redux-toolkit action is in pending state?

I have created an async thunk in redux using redux-toolkit as follows:
export const getMe = createAsyncThunk(`${SLICE_NAME}/me`, async () => {
return await fetchMe();
});
And, in my react useEffect, I am dispatching this action:
useEffect(() => {
if (!token) return;
if (user) return;
dispatch(getMe());
}, [dispatch, token, user]);
But my getMe action is getting dispatched twice.
I know, the reason is the dependencies of useEffect.
Even I tried using a state like so:
useEffect(() => {
if (!token) return;
if (user) return;
if (isLoading) return;
setIsLoading(true);
dispatch(getMe()).finally(() => { setIsLoading(false) });
}, [dispatch, token, user]);
But still getMe action is dispatched twice (looks like nearly at the same time!!)
So, I was thinking of comparing getMe.pending.something with something else. I don't know the exact piece of puzzle.
If that does not make sense, then please read this code:
const res = await dispatch(getMe());
if (getMe.fulfilled.match(res)) {
// api call was successful
}
As you see in above code, without maintaining any extra flags, we know exactly when API call is done and we know if it was successful.
I need something similar from redux-toolkit, that will tell me if action is in pending state.

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]);

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

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
},[ ])

Infinite loop on componentdidupdate with useEffect

I'm using redux and trying to fetch data when my component did update.
I'm using useEffect hook to track posts update and getting the state with useSelector.
I'm having issues as the component is making infinite fetching requests instead of a single request.
Anyone knows how I can stop it from making infinite requests
and make a single request if posts updated?
my code:
const posts = useSelector((state) => state.posts.posts);
useEffect(() => {
dispatch(getPosts(page));
}, [posts]);
image showing infinite fetching requests being made
From useEffect documentation
If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
So, dispatch(getPosts(page)) will be called on component mount as well when any of the dependency provided get changed, this will make an API request and fetch the posts of this page. Which will eventually update the state.posts.posts once the API is successful. As, the same state.posts.posts is given as dependency to the useEffect hook this will trigger the function to get executed again and the cycle goes on.
For example if you want to make the API call and fetch new posts when there's a change in the page you should provide page as dependency instead of posts as shown below
const posts = useSelector((state) => state.posts.posts);
useEffect(() => {
dispatch(getPosts(page));
}, [page]);
const posts = useSelector((state) => state.posts.posts);
useEffect(() => {
dispatch(getPosts(page));
}, []);
const updateNeeded = useSelector((state) => state.posts.updateNeeded);
useEffect(() => {
if (updateNeeded) {
dispatch(getPosts(page));
}
}, [updateNeeded]);
Change updateNeeded to true by a dispatch action when you want to fetch a new update, and when the update is fetched dispatch an action which will make this flag to false again.

Resources