Explanation: Get previous state - javascript

I'm reading the React Documentation and there is a topic that I didn't understand very well:
https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return <h1>Now: {count}, before: {prevCount}</h1>;
}
Demo:
https://codesandbox.io/s/get-previous-state-ref--hook-faq-w0f3k?file=/src/App.js
I'd like a better explanation about this. Kind, how it's works?. Thanks

There's three pieces here. A state, a ref, and an effect.
A state is a value that is remembered between renders, and it re-renders it's component when changed.
A reference (or ref) is a value that persists between renders like state does, but unlike state it's not watched for any changes and can be mutated freely. Changes to refs never cause renders.
An effect is simply a function that runs after a render, or after a render in one of its dependencies has changed since the previous render.
So putting that together here's what happens:
const [count, setCount] = useState(0);
Here a state is declared. Calling setCount with a new value will cause the component to render, and when it does, count will have the value that was set.
const prevCountRef = useRef();
Then declare the ref. This will hold a reference to the previous count value. Right now it no value (undefined), which makes sense considering there is no previous value when the component first renders.
useEffect(() => {
prevCountRef.current = count;
});
Now declare an effect which runs after every render (you can tell this because it has no dependencies (would be the second argument to useEffect()). When that runs it sets the current value of prevCountRef to whatever the count is.
So, on the first render, in pseudo code:
// render starts
count // 0
prevCountRef.current // undefined
// render finishes
effect runs {
prevCountRef.current is set to 0
}
So when the render is happening the count state is 0 and the previous count ref is undefined.
Second render:
setCount(1) // triggers the second render
// second render starts
count // 1
prevCount.current // 0
// second render finishes
effect runs {
prevCount.current is set to 1
}
So as you can see, during the render you always have the current count as as state, and the previous count as the ref. This works because the effect that sets the refs value always runs after the render is done, which allows it to be whatever the previous value was.

You can use componentDidUpdate to get the prev props and state. When you extend React.Component, it will call componentDidUpdate after each render. You define the function, and React will pass the prev props and prev state to you.
https://reactjs.org/docs/react-component.html#componentdidupdate

useEffect is called just after the component render.
The first time the component render prevCount is undefined because useEffect has not been called yet.
pre-render:
1) value: 0, prevCount: undefined
post-render:
useEffect is called:
2) value: 0, prevCount: 0 useRef don't cause a new rendering, thus the component is not updated. refCount will be shown as undefined.
when you change the count value the component gets re-render:
pre-render:
3) value: 1, prevCount: 0 useEffect is not called yet.
post-render:
4) value: 1, prevCount: 1 useEffect is called, prevCount increased but the component is not updated so prevCount will be shown the the value = 0;

Related

Setstate if fetch result is different

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.

Using const to declare useState function

function App() {
const [count, setCount] = useState(0);
function increase() {
setCount(count + 1);
console.log(count);
}
return (
<div>
<h1>{count}</h1>
<button onClick={increase}>+</button>
</div>
);
}
When I console.log the value of count, it increases by 1 even though I have declared it using const. How is it still being updated? I have a feeling it's related to the useState function, but I'm not sure.
Another thing is, if we can update using count + 1, why can't we do so using count++?
Thanks a lot for the guidance!
Code is here: https://codesandbox.io/s/g8dgv0?file=/src/components/App.jsx
React is fundamentally a rendering library, and the paradigm it uses is that the info that's rendered on the screen should flow directly from the current state stored in React. So, calling a state setter does two things:
It updates the internal state React has for that state value
It tells the component to re-render, so as to generate a new view based on the new updated state
Whenever a functional component re-renders, the function runs again - and useState can return different values depending on what the current value of the internal state is.
For a similar example in vanilla JS:
let internalValue = false;
const getValue = () => internalValue;
const setValue = (newValue) => {
internalValue = newValue;
setTimeout(App);
};
const App = () => {
const stateValue = getValue();
if (!stateValue) {
setValue(true);
}
console.log('app rendered with stateValue of', stateValue);
};
App();
Above, you can do const stateValue = getValue(); and get different values for stateValue depending on other factors - because the App function ran again.
Another thing is, if we can update using count + 1, why can't we do so using count++?
Because, as explained in the beginning of the answer, the view should flow from the state in React, and when updating the state, you need to tell React to re-render; reassigning a variable, by itself, doesn't have side effects. You need to call the state setter to update React's internal state and then re-render with the new state.
So, you need
setCount(count + 1);
and not
count++;
because only the former will result in a re-render (even if you had declared count with let).
The '0' value in your useState(0) is the default/initial value that the state will start with. From there you can modify it using setCount(value)
For your second question, from howtojs.io:
In React, we should not modify the state variables directly. We need
to pass the new data to the state variable modifier functions (such as
setCount), then React will modify the value of the state variable.
One more thing we need to remember is, when we do count++ in
JavaScript, the variable will be incremented after consuming the
variable first.
So, when we do setState(count++), first the value of count will be
passed to the setState function, then the value of count will be
incremented inside the function component (this is not recommended
way).
When I console.log the value of count, it increases by 1 even though I
have declared it using const.
When you are console logging the count state you are actually logging the unupdated current state value. In other words, nothing has changed yet. It's only at the end of the render cycle when enqueued state updates are processed that the component rerenders (i.e. calls the function body) with the updated state value.
How is it still being updated? I have a feeling it's related to the
useState function, but I'm not sure.
Yes, the useState hook returns the current state value and a function to call to enqueue an update to state.
const [count, setCount] = useState(0);
In the increase callback handler the setCount function is called with the next value you are requesting React to update the state to.
function increase() {
setCount(count + 1);
}
As described above, the update is enqueued and at then end of the render cycle is processed by React and triggers the component to rerender.
Another thing is, if we can update using count + 1, why can't we do so
using count++?
In React we absolutely DO NOT mutate state. The count++, if it could work and not throw an error, would still be a state mutation. Trying to use count++ is the same as trying to do count = count + 1 which we just simply don't do in React.
In more general Javascript terms, trying to post-increment with count++ while count is declared const would throw an error. When a variable is declared const this only means it can't have a value reassigned to it after initialization. This doesn't mean that the value currently assigned is static/constant. If we had declared let [count, setCount] = useState(0) then by Javascript standards doing count++ is perfectly legal.
We use the React state updater functions to enqueue state updates and generally return new object/value references.
The hook remains immutable in the scope of the function. When you change it, the component re-renders.
Type const in JS doesn't prevent you from making operations on the variable. Adding or subtraction of values will be interpreted without any issues. This the count is increasing.
However count++ shorthand is written for count = count + 1 where a assignment operation is taking place and it is prohibited by the type const.

React JS re-rendering

When a component re-renders as a result of calling the state setter function returned by useState, does the entire function component get called again or only parts of the function component get called?
function MyComponent() {
let [count, setCount] = useState(0);
const handleChange = () {
setCount((prevCount) => prevCount+1)
}
return (
<p onClick={handleChange}>
Count = {count}
</p>
)
}
In the above example, when we click the paragraph element, handleChange() gets called which in turn calls the state setter function setCount(). This causes our component to re-render. But when the component re-renders( or the function MyComponent gets called), the useState(0) function gets called again. This should set the initial value of count to 0 again. So shouldn't the counter be stuck at 0 instead of progressing to 1, 2, 3 and so on?
Whole functional component will be rerendered. If the props value which you are passing to the child component is not changing then you can prevent the rerendering of child component by using React.memo
Let say your component tree has parent and has children called childOne and childtwo.
if you change the state of the ChildOne;Only the ChildOne get rendered, Not Parent and childtwo get rendered. But if you change the state of the parent, whole component tree get retendered
This is the idea of hooks. They use closures.
If it was let count = 0, then yes, if would reset to 0, this is why it is let [count] = useState(0)
The whole thing gets re-render and everything is getting re-calculated.
The argument you pass in useState / useRef is used only once, when the component mounts. If it is expensive operation, it recalculate on each render (there are ways to improve this), but the value of it is used only once. Then when you call setState it updates the value.
They work like this:
let ref = {};
const useRef = (defaultValue) => {
if (!ref.current) {
ref.current = defaultValue
}
return ref
}
The hook argument is used only once.

Assigning value at useState() declaration vs useEffect()

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

In react when state changes, are useState and useEffect also updated?

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

Categories