I am using useEffects return() to clean up the state data in Redux when leaving a page. However, when changing view to another page, a child component that fetches new data still shows the old data from Redux for a short time before the new data shows up. The child component fetches data with a useEffect(), with an empty dependency list.
The clean up useEffect looks like this:
useEffect(() => {
return () => {
props.clearState()
}
}, [])
Where clearState() resets the redux-store to its initial values. The Redux Devtools shows that the state actually is cleared, but the child-component still renders with old props for a short time. I read that useEffect is asynchronous, could it be that the useEffect therefore is to slow? And how would you solve this without using the return() from useEffect?
Any help is appreciated, thanks!
Related
I use MongoDB to store data. The database works properly, as it can be tested in a when an endpoint is access. I use the useEffect hook to fetch data from the database. The code is as follows:
const [persons, setPersons] = useState([]);
useEffect(() => {
personService.getAll().then((response) => {
console.log(response.data);
setPersons(response.data);
console.log(persons);
});
}, []);
The first console.log() logs the result as expected, an array of JavaScript objects is returned. However, after assigning it to persons state using setPersons method, the state remains empty and nothing is rendered. What could be the problem here and how to make data appear on the page?
EDIT: I solved the problem it was because MongoDB returns fields as objects, even though they appear to be strings. I had to redefine the toJSON method using mongoose.
Because state only has value when component rerender. So you should put console.log into useEffect to check when component re-render success.
useEffect(() => {
console.log(persons);
}, [persons]);
Basically the setState actions are asynchronous and are batched for performance gains. This is explained in the documentation of setState as well.
So when you call setPersons(response.data), this action may not be even be completed, and you are logging persons.
If you log the persons outside the useEffect you should be able to see it, as component would rerender as soon as the state is updated, logging "persons".
I deleted my initial question because it was not very well explained from my side and led to unnecessary discuission and lost focus.
I am trying to build an app with react and redux and got stuck in an infinite loop while using useEffect and dispatch.
I build a codesandbox to reproduce the issue. Here is my work.
https://codesandbox.io/s/suspicious-morning-ew161
It seems like the issue is somehow related to line 8 in App.js but I can't explain why and how.
You should not write useSelector calls like that.
See https://react-redux.js.org/api/hooks#equality-comparisons-and-updates
Instead of
const { auth } = useSelector((state) => ({ ...state }));
write
const auth = useSelector((state) => state.auth);
Your other problem is
component={(props) => (
<TestProfilePage {...props} key={props.match.params.user_id} />
)}
this will create a new component on every rerender and React will unmount the full component tree and create a new one, effectively re-running your useEffect. Instead of component, use render. Also see the react-router docs on that.
Your code does two things:
it returns a new object on every selector call, which will make your component rerender always, even if there is no state change
it returns the full state, so even if you just returned state without the spread, it would rerender at any state change.
I have a component that is being called, then called again with updated information, but the var topics is not being updated by react. Can someone explain why the first example does not update on the screen, but the second one does? Does it have something to do with the useState call?
App.js calls DisplayQuestions once during initial render, then the page takes in user input and re-renders.
<DisplayQuestionsByTopic topics={topics} />
.....
topics var is NOT updated on screen:
export function DisplayQuestionsByTopic(props) {
console.log(`DisplayQuestionsByTopic`, props.topics)
const [topics, setTopics] = useState(props.topics) //<<<<<<<<
render(<h1>topics.join(',')</h1>)
}
topics var IS updated on screen:
export function DisplayQuestionsByTopic(props) {
console.log(`DisplayQuestionsByTopic`, props.topics)
render(<h1>props.topics.join(',')</h1>) //<<<<<<<<<<<
}
The argument passed to useState is only considered the first time the component renders - that is, on mount. On mount, the prop is put into the topics state. On further renders, changes to the prop don't result in a change to the state because the state has already been initialized to its initial value before.
While you could use useEffect to change the state in the child when the prop changes:
useEffect(() => {
setTopics(props.topics);
}, [props.topics]);
Unless you're using setTopics somewhere, it would make more sense just to render the prop, like you're doing in the final snippet.
If you do need to do setTopics in the child, consider passing down the state setter from the parent instead - that way, all the state is handled in the parent, and you don't have duplicate state holders in different components that need to be synchronized.
In the main component of a SPFx web part, I have a data load running in a React.useEffect
const [jobsGets, setJobsGets] = React.useState(0);
React.useEffect(() => {
{Some code to get data from SharePoint}
});
}, [jobsGets]);
So a refresh will happen when the value of "jobsGets" will change, right?
This component calls another component that calls another and the last has an event that should refresh by changing "jobsGets".
If I write a fanction and pass it down the props of each generation's component,
It ends running in the main component forever. So this is not a good solution.
How do I setJobsGets(jobsGets+1); from the grandchild components?
Any hint is welcome.
Thank you
you are likely updating jobs at grandChild with useEffect, that runs on mount and every update. if you pass a second empty array [] param, this update will run only after first render if that's your specific case, avoiding retriggers:
// at some granchild
useEffect(() => { ...logic here }, [])
if useEffect should run after on specific param's update, you pass that param as argument to the array. useEffect will run only on changes for the params dependencies set on [].
useEffect(() => { ...logic here }, [param])
you may also look at useContext for passing down state better at deep tree.
I have a component that displays a web map. I need to pass data in and out of the map component, but I never need to re-render it. I do all my work on the map outside of react - working directly with the mapping library.
Using functional components and hooks, how can I pass props in, act on them, and get data out of this component without rerendering? I've tried a combo of useEffect() and memo(), but they end up canceling out each other's use:
I have useEffect watching for changes of the relevant props and memo blocking renders, but since memo blocks renders, and useEffect only fires after a render, I have no way of handling prop updates in this component.
const Map = memo(props => {
useEffect(() => {
// run this on props update
console.log(props.selectedImg);
}, [props.selectedImg]);
//... map lib specific code using props
return <div id="mapDiv" />;
},
//prevent all rerenders using memo..
() => true
);
This doesn't work because memo prevents re-render and so useEffect is never fired even though props are updated.
How can I use hooks to work with props while entirely preventing this component from re-rendering? Perhaps this structure is not correct at all.
Thanks for the help!