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.
Related
In the following example, every click of the button should only render the next Number component, but instead it re-renders the whole list again. I would like to know the best practice to prevent the previous one from re-rendering. Thank you.
const Number = (props) =>{return (<div> {props.number} </div>)}
const NumberListUpTo = () =>{
const [listLength, setListLength] = useState(1);
return(
<>
<button onClick = {() => setListLength(listLength + 1)}>add one more</button>
{
Array.from({length: listLength}, (v, i) => i).map(
(i) => (<Number number = {i} />))
}
</>)
}
Use React.memo to memoize your components.
You should always provide a key for React components when you are rendering an array of elements.
Edit:
As React documentation says:
This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs.
Do not try to prevent renders or memoize every component. Try to optimize your code whenever you there is a performance issue. Optimization in React, using memo, useMemo, useCallback, etc) comes at a price; they take resources and may even slow down your app.
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.
I am trying to understand more about the dependency requirements when using a React hook. Using useMemo as an example, I can increment the counter and the value of dog is rendered as the same value as count. This is odd to me because foo is not referenced as a dependency and from my understanding, this closure should be stale.
From the React docs in regards to useMemo:
every value referenced inside the function should also appear in the dependencies array
It doesn't seem like this^ is a requirement. If someone could help me better understand how the closure works in this particular case, I'd appreciate it.
function App() {
const [count, setCount] = useState(0);
const foo = useMemo(() => count, [count]);
const dog = useMemo(() => foo, [count]); // incrementing count updates this value
return (
<div className="App">
{dog}
<h1>Hello CodeSandbox</h1>
<h2>You clicked {count} times!</h2>
<button onClick={() => setCount(count - 1)}>Decrement</button>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Because of the way you've set things up, it happens to be that every time count changes, foo also changes. So even though you're checking the wrong variable, there's a 1:1 correspondance between times you want the memoization to break and times it actually breaks.
You shouldn't rely on this though. It is rare that things will be as simple as your example, and so you will easily make mistakes. To reliably get it to work, you should populate the dependency array with the values you actually depend on, not things which are merely synchronized with the values you depend on.
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 understand that, within your React component, it's always good to render a component rather than use a function that renders some JSX.
What I mean is,
Doing this :
export default function App() {
const [count, setCount] = useState(0);
const someRenderFunction = () => <p>Hey</p>;
return (
<div className="App">
{someRenderFunction()}
<button
onClick={() => {
setCount((c) => c + 1);
}}
>
Increment{" "}
</button>
<p>{count}</p>
</div>
);
}
is NOT encouraged. The render function should be exported into it's own Component, like this :
const HeyComponent = () => <p>Hey</p>
export default function App() {
const [count, setCount] = useState(0);
return (
<div className="App">
<HeyComponent />
<button
onClick={() => {
setCount((c) => c + 1);
}}
>
Increment{" "}
</button>
<p>{count}</p>
</div>
);
}
But I never really understood the benefits of this refactor. I tried placing checking the re-render behaviours, unmount behaviours. Still didn't see any difference.
Can anyone explain in detail why this refactor is necessary/beneficial ?
This is all about components, so components should be reusable and should follow the DRY principle, in your case that seems to be so simple and just as you said will prevent the someRenderFunction() to be re-rendered if there aren't any changes to that component in the virtual dom so the best practices are to use <X /> syntax always or for some cases const Y = <X /> also is more readable. testing is another reason to create more components and make them more decoupled. imagine here you need to pass props to your someRenderFunction() so you will lose the jsx feature for passing props as <X prop={PROP}/>.
The actual difference between the two is that the function returning JSX is not a component, so it does not have its own state. When you use useState in the function, the state change will cause the App to be re-rendered.
Two reasons why the second way is better than the first way are:
The function which defines the <p>Hey</p> JSX (named someRenderFunction in the first way, and HeyComponent in the second) is moved outside of the component App in the second way. As of now, there's no benefit, but if you later wanted to extend someRenderFunction/HeyComponent to have normal component lifecycle that can be hooked into, moving it outside of App would be essential. For example, if you wanted the function/component to keep track of its own state, the first way would cause someRenderFunction's state to get reset every time App renders. More info on this problem can be found here: https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-unstable-nested-components.md, and here: https://stackoverflow.com/a/64211013/693855.
HeyComponent would be seen as a valid component whereas someRenderFunction would not, because component names need to start with a capital letter (this is mentioned here https://reactjs.org/docs/components-and-props.html#rendering-a-component in the Note at the bottom of the section). If you used hooks in your function, and it was not seen as a valid component by React, it would be breaking the "Rules of Hooks" set by React (https://reactjs.org/docs/hooks-rules.html#only-call-hooks-from-react-functions). I'm not sure if this would cause any bugs, but it is frowned upon by React -- if you have the "react-hooks/rules-of-hooks" ESLint rule enabled (from https://www.npmjs.com/package/eslint-plugin-react-hooks), you would see an error like this: ESLint: React Hook "useState" is called in function "someRenderFunction" that is neither a React function component nor a custom React Hook function.(react-hooks/rules-of-hooks)
The code which renders a component is followed as best practice.
Using Components we can pass state values to child component and can be used to display data. The same component can be re-used in other places.
If you have to just display a small function it can be used too if it shows constant information.