I have been using these two ways interchangeably however I am not sure which one is the more correct. their behavior seems to be the same but I am sure there is a use case for each. Anyone can help me understand what's the proper use for each case?
const [customer, setCustomers] = useState(props.location.state);
useEffect(() => {
setCustomers(props.location.state);
}, []);
You should normally stick to the first one. Calling the setter of useState may lead to undesired re-renders and decreased performance.
In the first block the customer is initialised directly and no re-render happens. The setCustomer method will change the state and rerender the component. In the end the whole function will run twice which you can verify with a console.log.
const [customer, setCustomers] = useState(0);
useEffect(() => {
setCustomers(15);
}, []);
console.log(customer) // will first output 0 and then 15
Assuming in the second case, you have this as your useState statement:
const [customer, setCustomers] = useState();
The second one sets the value of customer on componentDidMount. So in the initial render, you will not have the appropriate value in your customer variable.
But yes, very soon after that the correct value will be set because of the code written in useEffect.
To clear it up, there will be 2 renders here (because the state variable value changes). In the first one, that won't be the case since the state variable has only one value from beginning.
The first one is more effective.
const [customer, setCustomers] = useState(props.location.state);
If you use second one (by using useEffect), your component will be re-rendered again.
That's, your state variable customer will be updated in useEffect after DOM is initially rendered, this leads the 2nd re-render of the component.
But if you want customer to be updated by props.location.state, you need to add useEffect hook like the following.
useEffect(()=> {
setCustomers(props.location.state);
}, [props.location.state]);
Setting the state's default value upon declaring it is probably the more correct way to go, since it does not trigger a re-render.
Every time you call a setState your component will be re-rendered, so when you do so in the useEffect, you will trigger an unnecessary re-render upon the component mounting, which could be avoided by doing the good ol'
const [value, setValue] = useState(props.location.state)
While of course there are exceptions and many different use cases, setting an initial state in a useEffect is more useful, for example, when you have values you'd expect to change regardless of your component (for example from an external asynchronous API call):
const [value, setValue] = useState(valueExpectedToChange)
useEffect(() => {
setValue(valueExpectedToChange) // will trigger the rerender only when valueExpectedToChange changes
}, [valueExpectedToChange])
Related
I am fetching data from some url each second. If data is different than data inside my state, I want to update that state and rerender, and if data is same I do not want to do anything
First I tried most obvious thing, to set interval in useEffect on mount, but it do not work since state always return initial value which is obvious.
Second I created two states, one that holds data and other temp one, then I update temp state and on its useEffect I compare values. It does work but I still got rerender when updating that temp state, and whole point was to not have unnecessary rerender.
Third thing I tried is holding that temp data inside variable or ref, but useEffect is not working on them.
Here is last code I tried with ref so you get idea of what I am trying to do:
const MyComp = () => {
const [data, setData] = useState([])
const tempDataRef = useRef([])
useEffect(() => {
apiFetch().then((returnedArray) => {
tempDataRef.current = returnedArray
})
}, [])
useEffect(() => {
// in this solution using ref, this useeffect is not firing
if(JSON.stringify(tempDataRef.current) != JSON.stringify(data)) {
setData(tempDataRef.current)
}
}, [tempDataRef.current])
return (
<div>
{JSON.stringify(data)}
</div>
)
}
whole point was to not have unnecessary rerender.
tl;dr - it's not possible. Component has to be aware that the data has changed.
setData(tempDataRef.current) code does not fire at all, since useEffect does not listen to useRef updates.
You have two options - either store the data in the state, or keep the useRef but then you will have to apply some interval to check if the data has changed and if so - re-render the component. But of course this is pointless, because you will end up with unnecessary re-renders.
If you are worried about performance drop caused by this "extra" re-render when fetching the data you can always memoize your children so they won't re-render unnecessarily.
When the state changes, to what extent the component is updated? Let's say, when state A changes, I can read the console log of render in Component. I'm wondering what happens to the statement useState since the initial value is set at 1 because the initial value should not be ignored. When I call someFunction, a now becomes 2, but if rerendering occurs, what does happen to const [a,setA] = useState(1)?
For useEffect, when state A changes, I also think useEffect is re-claimed (and for sure, dependency has changed!), but what happen to the previously stated version of useEffect?
Whenever I click the button, new version of useState and useEffect are generated, and what happen to the old versions of these pairs? Are they being stored into some kind of memory of browsers? Judging from the react debugger, we can navigate to the previous look with the previous state values, which means the snapshots of the states are somehow stored. I am super curious where they are! If that's true, when certain amount state changes exceeds the memory limit, would our app be in crisis?
Looking forward to getting any feedbacks about this question!
const Component = () => {
console.log('render component');
const [a, setA] = useState(1);
const someFunction = () => {
console.log('some function')
setA(prev=>prev+1)
}
useEffect(() => {
console.log('use effect')
console.log(a);
}, [a])
return <>
<div onClick={someFunction}>Click</div>
</>
}
what happens to the statement useState since the initial value is set at 1 because the initial value should not be ignored
When a component first mounts, any useStates used inside it create a value mapped to that state in React's internals. The initial value for that is the argument passed to useState. It may be like an array. For example, if you have
const [a, setA] = useState(1);
const [b, setB] = useState(5);
Then, once the component renders, React is storing internally something like
[1, 5]
corresponding to the first and second states.
When you set state, the corresponding value in React's internals changes. For example, if you ran, once:
setA(prev=>prev+1)
React would then have
[2, 5]
And then the component would re-render. On re-renders, the initial state (passed to useState) is ignored - instead, the stateful value is taken from the value in React internals. So with
const [a, setA] = useState(1);
const [b, setB] = useState(5);
on re-render, a evaluates to 2, and b evaluates to 5.
what happen to the old versions of these pairs?
The old values may get garbage collected if nothing can reference them anymore. (The state setter functions are stable.)
For useEffect, when state A changes, I also think useEffect is re-claimed (and for sure, dependency has changed!), but what happen to the previously stated version of useEffect?
Yes, with a new render, prior useEffect callback functions will eventually get garbage collected, with React replacing them internally with the new effect callback function(s) for the current render.
when certain amount state changes exceeds the memory limit, would our app be in crisis?
No, because unused objects from prior renders will get garbage collected.
The following snippet will log Just got GC'd! when enough objects have piled up in memory and they get garbage collected:
const r = new FinalizationRegistry(() => {
console.log('Just got GCd!');
});
const App = () => {
const [someObj, setSomeObj] = React.useState({ num: 0 });
setTimeout(() => {
r.register(someObj);
setSomeObj({ num: someObj.num + 1 });
}, 100);
return JSON.stringify(someObj);;
};
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
The argument passed to useState is the initial state much like setting
state in constructor for a class component and isn't used to update
the state on re-render
If you want to update state on prop change, make use of useEffect hook
React.useState does not reload state from props
Lets say I have the following component using react hooks
const Component = (props) => {
[state1, setState1] = useState();
[state2, setState2] = useState();
useEffect(()=>{
// use state1 and state2 as param to fetch data
...
}, [state1, state2])
onSomethingChange = () => {
setState1(...);
setState2(...);
}
return (..);
}
When onSomethingChange triggers, I thought it would call the side effect function twice since I update 2 different state vars in the same dependency array. And I was going to refactor those 2 vars into 1 object but I thought I would test them as 2 separate vars first.
What I observed is the side effect function only gets executed once even when I update 2 different state vars in the same dependency array, this is what I wanted but I don't know why. Could someone please explain how it works under the bonnet?
That's because React sometimes may batch multiple state changes into one update for performance. React will batch state updates if they're triggered from within a React-based event, that's the reason why your side effect block is called only once. For more information, you may refer this thread: https://github.com/facebook/react/issues/14259
React batches state updates under the hood.
That simply means that calling
setState1(...);
setState2(...);
in the same synchronous (!) execution cycle (e.g. in the same function) will NOT trigger two component re-render cycles.
Instead, the component will only re-render once and both state updates will be applied simultaneously.
Not directly related, but also sometimes misunderstood, is when the new state value is available.
Consider this code:
console.log(name); // prints name state, e.g. 'Doe'
setName('John');
console.log(name); // ??? what gets printed? 'John'?
You could think that accessing the name state after setName('John');
should yield the new value (e.g. 'John') but this is NOT the case.
The new state value is only available in the next component render cycle (which gets scheduled by calling setName()).
I have component that uses both useState() and other custom hooks multiple times.
I want to act based on these state values. I could do that directly in the function component's body:
const MyComponent = () => {
const [someState, setSomeState] = useState(false);
const [otherState, setOtherState] = useState(false);
const customHookValue = useCustomHook();
if (someState) foo();
const foo = () => setOtherState(!otherState);
if (customHookValue > 10) bar();
const bar = () => setSomeState(somestate > customHookValue);
}
However, every time someState changes (and a re-render happens) the second conditional also runs, causing bar() to run if the conditional passes. This feels unnatural. Logically, bar() should only run when customHookValue changes, as someState only changes if customHookValue has changed since last render.
In summary, a re-render caused by a change to someState should not cause a bunch of unrelated state setting functions to run again. Even though re-running them causes no change in the outcome of the program, it is not logically right. They only need to re-run when their corresponding conditional changes. It could effect performance.
This must be a common challenge in React. I am quite new to it, so I do not know what the approach to solve this would be.
Questions
How would I solve the above in the recommended manner?
Would I have to wrap every conditional in a useEffect or a useMemo?
EDIT:
Updated the second conditional, to make my question clearer (it should depend on customHook).
CLARIFICATION:
As it might not have been clear, my issue is as follows. When state changes, a re-render occurs. This causes all functions in the component's body to re-run. Now, if we have many useState in a component, and only one changes, a bunch of potentially unrelated state-changing and potentially expensive functions I have defined in the components body will run. These state-changing functions would only have to run if the state values they are trying to set has changed. If the values they are setting has not changed, it is unnecessary for them to run again. But, the re-render reruns all functions in the component's body regardless.
It looks like (as others have suggested) you want useEffect:
useEffect(() => {
if (someState) {
setOtherState(!otherState)
}
}, [someState, otherState])
useEffect(() => {
if (customHookValue > 10) {
setSomeState(someState > customHookValue)
}
}, [customHookValue])
Since you only want the setSomeState to run if customHookValue changes, make it the only item in the dependencies array passed to useEffect.
The exhaustive-deps eslint-plugin-react-hooks will complain about the second useEffect, since the function depends on the value of someState, even though someState will only potentially change if customHookValue changes. I also wouldn't worry about things potentially affecting performance until they do. I don't know a ton about the internals of React, but it does some things under the hood to avoid re-renders it doesn't need to do, and can do multiple renders before an actual update is painted.
I am trying to update context once a Gatsby page loads.
The way I did it, the context is provided to all pages, and once the page loads the context is updated (done with useEffect to ensure it only happens when the component mounts).
Unfortunately, this causes an infinite render loop (perhaps not in Firefox, but at least in Chrome).
Why does this happen? I mean, the context update means all the components below the provider are re-rendered, but the useEffect should only run once, and thats when the component mounts.
Here is the code: https://codesandbox.io/s/6l3337447n
The infinite loop happens when you go to page two (link at bottom of page one).
What is the solution here, if I want to update the context whenever a page loads?
The correct answer for this issue is not to pass an empty dependency array to useEffect but to wrap your context's mergeData in a useCallback hook. I'm unable to edit your code but you may also need to add dependencies to your useCallback like in my example below
import React, { useState, useCallback } from "react"
const defaultContextValue = {
data: {
// set initial data shape here
menuOpen: false,
},
mergeData: () => {},
}
const Context = React.createContext(defaultContextValue)
const { Provider } = Context
function ContextProviderComponent({ children }) {
const [data, setData] = useState({
...defaultContextValue,
mergeData, // shorthand method name
})
const mergeData = useCallback((newData) {
setData(oldData => ({
...oldData,
data: {
...oldData.data,
...newData,
},
}))
}, [setData])
return <Provider value={data}>{children}</Provider>
}
export { Context as default, ContextProviderComponent }
The selected answer is incorrect because the react docs explicitly say not to omit dependencies that are used within the effect which the current selected answer is suggesting.
If you use es-lint with the eslint-plugin-react-hooks it will tell you this is incorrect.
Note
If you use this optimization, make sure the array includes all values
from the component scope (such as props and state) that change over
time and that are used by the effect. Otherwise, your code will
reference stale values from previous renders. Learn more about how to
deal with functions and what to do when the array changes too often.
https://reactjs.org/docs/hooks-effect.html
Is it safe to omit functions from the list of dependencies? Generally
speaking, no. It’s difficult to remember which props or state are used
by functions outside of the effect. This is why usually you’ll want to
declare functions needed by an effect inside of it. Then it’s easy to
see what values from the component scope that effect depends on:
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
By default, useEffect runs every render. In your example, useEffect updates the context every render, thus trigger an infinite loop.
There's this bit in the React doc:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
So applies to your example:
useEffect(() => {
console.log("CONTEXT DATA WHEN PAGE 2 LOADS:", data)
mergeData({
location,
})
- }, [location, mergeData, data])
+ }, [])
This way, useEffect only runs on first mount. I think you can also leave location in there, it will also prevent the infinite loop since useEffect doesn't depend on the value from context.