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.
Related
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])
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
I saw this custom hook when reading some blog post
const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
};
I made a codesandbox to experiement with it https://codesandbox.io/s/weathered-surf-eenqo?file=/src/App.js
and indeed it works. It preserves the previous input value I had there.
And I tried to replicate this hook using useState
const usePrevious2 = (value) => {
const [prevValue, setPrevValue] = useState();
useEffect(() => {
setPrevValue(value);
}, [value]);
return prevValue;
};
However the value returned by usePrevious2 is in sync with the current state. I wonder what is the underline mechanism for the differences here? Why is that the ref can preserver the previous state but not another useState. Can someone explain this to me?
The issue with using useState here is that state updates result in re-renders.
If you use usePrevious / useRef and change the input, there will be one state update, when the input value changes - and after that state update occurs, the usePrevious will shortly schedule a ref.current = value assignment, but such assignment will not cause a re-render.
In contrast, with usePrevious2 / useState, when you use setPrevValue, a re-render will occur. When the input field changes, there will be 2 re-renders; one when the input field changes, and then another after the useEffect callback runs and calls setPrevValue. So the usePrevious2 will only be "out of date" for a very short time, until its effect hook runs and sets the state again.
I have an array of certain objects in my redux store and I retrieve it like so:
const storeExpenses = useSelector(({ expenses }: RootState) => expenses.items));
I then save those expense objects also in the components local state since I have to further filter them without wanting to change them in the store.
const [expenses, setExpensesInState] = useState<Expense[]>(storeExpenses);
Now, when my store expenses are updated somewhere else, I want to refresh the local state as well, like so:
useEffect(() => {
setExpensesInState(storeExpenses));
}, [storeExpenses]);
However this results in an endless loop of the useEffect hook.
My assumption is that when I use setExpensesInState, I trigger a redraw of the component which then sets the expensesInStore variable which in turn triggers again the useEffect and so on. Is this assumption correct or am I misunderstanding anything else? And how would I resolve this to achieve what I need?
I am new to using hooks in React. I am trying to fetch data when the component first mounts by utilizing useEffect() with a second parameter of an empty array. I am then trying to set my state with the new data. This seems like a very straightforward use case, but I must be doing something wrong because the DOM is not updating with the new state.
const [tableData, setTableData] = useState([]);
useEffect(() => {
const setTableDataToState = () => {
fetchTableData()
.then(collection => {
console.log('collection', collection) //this logs the data correctly
setTableData(collection);
})
.catch(err => console.error(err));
};
setTableDataToState();
}, []);
When I put a long enough timeout around the setTableData() call (5ms didn't work, 5s did), the accurate tableData will display as expected, which made me think it may be an issue with my fetch function returning before the collection is actually ready. But the console.log() before setTableData() is outputting the correct information-- and I'm not sure how it could do this if the data wasn't available by that point in the code.
I'm hoping this is something very simple I'm missing. Any ideas?
The second argument passed to useEffect can be used to skip an effect.
Documentation: https://reactjs.org/docs/hooks-effect.html
They go on to explain in their example that they are using count as the second argument:
"If the count is 5, and then our component re-renders with count still
equal to 5, React will compare [5] from the previous render and [5]
from the next render. Because all items in the array are the same (5
=== 5), React would skip the effect. That’s our optimization."
So you would like it to re-render but only to the point that the data changes and then skip the re-render.