Taking this simple React counter example:
const { useState } = React;
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
console.log("Example")
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(1)}>
Click me
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Example />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I've intentionally set the setCount handler to just const value for experimental reasons. And there is something very strange to me - the App re-renders the second time when I click the button the second time! (I'm getting Example output on the first and ALSO on the second click!)
My BIG question is HOW can it happen if in the case of the second click the value of the count variable HASN'T changed since the first click!? (by clicking the first time is set to just 1 and the second time ALSO to 1!)
When I click the third time and more it seems to work as expected - there are no further re-renders...
Can someone please explain me the reason of this extra render after the secon click?
P.S.
PLEASE don't tell me that the cause of this may be the react strict mode - As anyone can CLEARLY see I'm NOT using the strict mode anywhere!!!
Ref : https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
try this---
In place of setCount(1) make it setCount(count++) or setCount(count+1)
Now it will work .
Beacuse you are just setting value of count as 1 on every click.
Related
I have sample code below:
function App() {
console.log("render");
const [val, setVal] = React.useState(0);
return (
<div className="App">
<h1>{val}</h1>
<button onClick={() => setVal(12)}>Update with same value</button>
</div>
);
}
When I click a button multiple times, the console log 3 times with 'render' message. For me, it should be 2 times only:
1 for first render
2 for the update from val 0 to 12 (when click button)
and since this time, it should not re-render because the same value (12) is updated to val.
But why it appears 3 times? That mean it still re-render one more time despite the same value was updated.
Anyone who know please explain this, thanks in advance.
P/S: I've figured out that it's only cause an extra re-render when the value changed then has been updated with the same
function App() {
console.log("render");
const [val, setVal] = useState(4);
return (
<div className="App">
<h1>{val}</h1>
<button onClick={() => {
setVal(val => val + 1)
}}>Update</button>
<button onClick={() => {
setVal(val => val)
}}>Update with same value</button>
</div>
);
}
When first click on 2nd button, no re-render call, but if you click the 1st button then 2nd button, 2nd button cause 1 extra re-render
This thread may help you : React: Re-Rendering on Setting State - Hooks vs. this.setState
Also, you can check the second paragraph over here which says:
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
React can’t guess the ouput of render() won’t change: it has to render() again and compare the results with the previous render().
Then the magic happens: if there are no differences, the DOM is not updated; if there are differences, it tries to only create/destroy elements as needed, because that’s the expensive part, not running render() — well it should not be.
Changing the state normally triggers a call to render() (not necessarily DOM modifications) — but if you want control over that behavior, define shouldComponentUpdate.
Note: That goes for non-hook components. However, I didn’t know the behavior of hooks was slightly different from that of a regular component: it seems that you’re right in expecting setState not to trigger a render when the value is unchanged — see Yash Joshi's answer.
I'm trying to understand how React works under the hood and there is something that I cannot found anywhere and that I can't figure out by myself.
I know that whenever we change the state of a component using setX, React compares the new representation of the Virtual DOM with the one it already has and, if there is anything to change in the real DOM, then it goes ahead and flushes those changes.
To see this in action I created a component like this one:
<div id="root"></div><script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/#babel/standalone#7.16.6/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">
const {useEffect, useState} = React;
const App = () => {
const [counter, setCounter] = useState(1);
console.log('in component');
useEffect(() => {
console.log('in useEffect');
});
return (
<>
<div>{counter}</div>
<button onClick={() => setCounter(2)}>
Increment
</button>
</>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
</script>
Initially, when the component mounts, it is logged:
in component
in useEffect
The same when I click the button once. And if I click one more time, it just logs:
in component
I guess this time we don't see the in useEffect because React correctly came to the conclusion that there is nothing to change in the DOM.
Ok. It makes completely sense.
The thing is the next time I click, I'd expect to see again the in component, but nothing happens. And the same applies to the following clicks.
Why does this happen?
When you click the button a second time, nothing happens, because your code sets the counter to the fixed value 2:
onClick={() => setCounter(2)}
So you can click it a million times, but the value will stay 2, and so the virtual DOM that you return won't be different from the previous render call, and so your useEffect doesn't need to run, because nothing changed.
As per the documentation, useEffect runs after render, and is intended as a mechanism to run "some code side effect" as a result of your component updated. If nothing changed, then there should be no side effects.
I have the following code (CodeSandbox):
function App() {
const [blah, setBlah] = useState(true);
console.log('BLAH', blah);
setBlah(true);
return <button onClick={() => setBlah(true)}>Blah</button>;
}
I understand that setBlah(true) at the top level of the component is causing too many re-renders. What I don't understand is if you comment out the top level setBlah(true) and start mashing the "Blah" button, why does the component not re-render? Both are setting the state of blah to true over and over, yet only the top level setBlah(true) causes a re-render.
From the React docs on Bailing out of a state update: "Note that React may still need to render that specific component again before bailing out." The key word here is "may", which to me means that my situation is valid according to the docs. So the question might become, under what conditions will calling setState, with the same value that is already set, cause a re-render? It would be useful to know this as one might want to put logic in front of their setState method to check if the value is the same as the current so as to not cause an erroneous re-render.
Via the documentation you linked to in your question:
If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)
You can read there that React uses Object.is when comparing previous and new state values. If the return value of the comparison is true, then React does not use that setState invocation in considering whether or not to re-render. That is why setting your state value to true in the onClick handler doesn't cause a rerender.
That said, unconditionally calling a setState function at the top level of any component is always an error, because it initiates the reconciliation algorithm (infinitely). It seems to me that this is the core of your question, and if you want to learn about React Fiber (the implementation of React's core algorithm), then you can start here: https://github.com/acdlite/react-fiber-architecture
Here is another note from the React documentation (which needs to be updated for functional components):
You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop.
↳ https://reactjs.org/docs/react-component.html#componentdidupdate
Explaining how class component lifecycle methods translate to functional components is out of scope for this question (you can find other questions and answers on Stack Overflow which address this); however, this directive applies to your case.
Here's a snippet showing that your component only renders once when the erroneous setState call is removed:
<script src="https://unpkg.com/react#17.0.2/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#17.0.2/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone#7.16.3/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel" data-type="module" data-presets="react">
const {useRef, useState} = React;
function Example () {
const renderCountRef = useRef(0);
renderCountRef.current += 1;
const [bool, setBool] = useState(true);
return (
<div>
<div>Render count: {renderCountRef.current}</div>
<button onClick={() => setBool(true)}>{String(bool)}</button>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>
From my comment - Since you've already set the default of blah to true -
const [blah, setBlah] = useState(true);
Mashing the button is not actually changing the state since it's already true. However, if you do something like:
return <button onClick={() => setBlah(!blah)}>Blah</button>;
You'll see it switch between true and false in the console every time you hit the "Blah" button.
It's the
setBlah(true);
in the constructor that's causing the loop. It already set the state to true, then you're telling it to set it again in the same constructor.
I have sample code below:
function App() {
console.log("render");
const [val, setVal] = React.useState(0);
return (
<div className="App">
<h1>{val}</h1>
<button onClick={() => setVal(12)}>Update with same value</button>
</div>
);
}
When I click a button multiple times, the console log 3 times with 'render' message. For me, it should be 2 times only:
1 for first render
2 for the update from val 0 to 12 (when click button)
and since this time, it should not re-render because the same value (12) is updated to val.
But why it appears 3 times? That mean it still re-render one more time despite the same value was updated.
Anyone who know please explain this, thanks in advance.
P/S: I've figured out that it's only cause an extra re-render when the value changed then has been updated with the same
function App() {
console.log("render");
const [val, setVal] = useState(4);
return (
<div className="App">
<h1>{val}</h1>
<button onClick={() => {
setVal(val => val + 1)
}}>Update</button>
<button onClick={() => {
setVal(val => val)
}}>Update with same value</button>
</div>
);
}
When first click on 2nd button, no re-render call, but if you click the 1st button then 2nd button, 2nd button cause 1 extra re-render
This thread may help you : React: Re-Rendering on Setting State - Hooks vs. this.setState
Also, you can check the second paragraph over here which says:
Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.
React can’t guess the ouput of render() won’t change: it has to render() again and compare the results with the previous render().
Then the magic happens: if there are no differences, the DOM is not updated; if there are differences, it tries to only create/destroy elements as needed, because that’s the expensive part, not running render() — well it should not be.
Changing the state normally triggers a call to render() (not necessarily DOM modifications) — but if you want control over that behavior, define shouldComponentUpdate.
Note: That goes for non-hook components. However, I didn’t know the behavior of hooks was slightly different from that of a regular component: it seems that you’re right in expecting setState not to trigger a render when the value is unchanged — see Yash Joshi's answer.
I'm trying to get a functional component to force a rerender whenever the testFn executes. I thought to use state to do this (if there's a better way then please speak up), which appears to successfully force a rerender but only twice, then nothing.
I built a simple demo to emulate the issue as using my real app is too difficult to demonstrate but the same principles should presumably apply (my real demo fetches data when the function executes and displays it on the page, but it's not rerendering and I have to refresh the page to see the new data, hence why I want to trigger a rerender).
import React, { useState } from "react";
const App = () => {
const [, rerender] = useState(false);
const testFn = () => {
console.log("test Fn");
rerender(true);
};
return (
<div>
<p>test</p>
<button onClick={testFn}>clickk</button>
{console.log("render")}
</div>
);
};
export default App;
I've also made a Stackblitz demo for conveinence.
Can anyone solve this demo or think of a better way of implementing it?
Thanks for any help here.
It triggers a re-render when the state changes.
The first time you click the button you change the state from false to true so a rerender is triggered.
Subsequent clicks you change it from true to true which isn't a change, so it doesn't.
You could toggle it:
const [render, rerender] = useState(false);
and
rerender(!render);
so it actually changes.
… but this smells of being an XY Problem, and you should probably be changing something which is actually being rendered.
State is the right way, since state changes are the primary way to cause re-renders. I'd increment a counter:
const [, setCount] = useState(0);
// force a re-render by running:
setCount(c => c + 1)
But this is still a very odd thing to do. In almost all cases, a much more elegant solution will be implementable, such as by putting data that changes into state, and calling the state setter when the data updates.