How to prevent component from re-rendering? (with MWE) - javascript

How can I prevent a component from re-rendering when its props are not changed?
Code sandbox link/minimal working example. Move your cursor over Canvas and see that the console is logged with many "Canvas re-rendered" messages.
At the top-level, I pass in a const function as a handler to the Canvas. The onMouseMoveHandler updates App's state so that it can update the Details component:
<div className="App">
<Canvas onMouseMoveHandler={onMouseMoveHandler} />
<Details passedProp={getValue} />
</div>
I need it so that Canvas is not re-rendered because the HTML canvas element inside has user-drawn lines. How can I do this?
I would have thought that using React.memo would make it like a pure component so that it is not re-rendered.
I am a very beginner to React and if I am not thinking of the right code structure, let me know how to fix.

In your App.js every time you call setValue, your App component rerenders and creates a new onMouseMoveHandler function.
Your Canvas component uses React.memo but since on each rerender onMouseMoveHandler gets a new reference, it also rerenders.
In order to prevent that you have to wrap your onMouseMoveHandler with useCallback. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. .
Here's the example:
const onMouseMoveHandler = useCallback(() => {
setValue(Math.random());
}, []);

If you wrap onMouseMoveHandler in a useCallback that should solve it. The problem is that when the state changes, your App-component re-renders and the onMouseMoveHandler function gets a new reference. And seeing as it has a new reference, this will cause the Canvas to re-render as well because it sees this as a prop change. Using the useCallback hook you guarantee that the function reference stays intact between renders.

You need to use callBack hook, because every time you change the state of the component App, you redefine the function onMouseMoveHandler so the value change and the props onMouseMoveHandler change so the component Canvas need to re-render.
You can check the modifications here:
https://codesandbox.io/s/determined-water-72uq4?file=/src/App.js:362-380

Related

Entire NextPage from NextJs is executed after Button Change. It should only executed the change. What am I doing wrong? [duplicate]

Does React re-render all components and sub components every time setState() is called?
If so, why? I thought the idea was that React only rendered as little as needed - when state changed.
In the following simple example, both classes render again when the text is clicked, despite the fact that the state doesn't change on subsequent clicks, as the onClick handler always sets the state to the same value:
this.setState({'test':'me'});
I would've expected that renders would only happen if state data had changed.
Here's the code of the example, as a JS Fiddle, and embedded snippet:
var TimeInChild = React.createClass({
render: function() {
var t = new Date().getTime();
return (
<p>Time in child:{t}</p>
);
}
});
var Main = React.createClass({
onTest: function() {
this.setState({'test':'me'});
},
render: function() {
var currentTime = new Date().getTime();
return (
<div onClick={this.onTest}>
<p>Time in main:{currentTime}</p>
<p>Click me to update time</p>
<TimeInChild/>
</div>
);
}
});
ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>
Does React re-render all components and sub-components every time setState is called?
By default - yes.
There is a method boolean shouldComponentUpdate(object nextProps, object nextState), each component has this method and it's responsible to determine "should component update (run render function)?" every time you change state or pass new props from parent component.
You can write your own implementation of shouldComponentUpdate method for your component, but default implementation always returns true - meaning always re-run render function.
Quote from official docs http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate
By default, shouldComponentUpdate always returns true to prevent
subtle bugs when the state is mutated in place, but if you are careful to
always treat the state as immutable and to read-only from props and state
in render() then you can override shouldComponentUpdate with an
implementation that compares the old props and state to their
replacements.
Next part of your question:
If so, why? I thought the idea was that React only rendered as little as needed - when the state changed.
There are two steps of what we may call "render":
Virtual DOM renders: when render method is called it returns a new virtual dom structure of the component. As I mentioned before, this render method is called always when you call setState(), because shouldComponentUpdate always returns true by default. So, by default, there is no optimization here in React.
Native DOM renders: React changes real DOM nodes in your browser only if they were changed in the Virtual DOM and as little as needed - this is that great React's feature which optimizes real DOM mutation and makes React fast.
No, React doesn't render everything when the state changes.
Whenever a component is dirty (its state changed), that component and its children are re-rendered. This, to some extent, is to re-render as little as possible. The only time when render isn't called is when some branch is moved to another root, where theoretically we don't need to re-render anything. In your example, TimeInChild is a child component of Main, so it also gets re-rendered when the state of Main changes.
React doesn't compare state data. When setState is called, it marks the component as dirty (which means it needs to be re-rendered). The important thing to note is that although render method of the component is called, the real DOM is only updated if the output is different from the current DOM tree (a.k.a diffing between the Virtual DOM tree and document's DOM tree). In your example, even though the state data hasn't changed, the time of last change did, making Virtual DOM different from the document's DOM, hence why the HTML is updated.
Yes. It calls the render() method every time we call setState only except when shouldComponentUpdate returns false.
Even though it's stated in many of the other answers here, the component should either:
implement shouldComponentUpdate to render only when state or properties change
switch to extending a PureComponent, which already implements a shouldComponentUpdate method internally for shallow comparisons.
Here's an example that uses shouldComponentUpdate, which works only for this simple use case and demonstration purposes. When this is used, the component no longer re-renders itself on each click, and is rendered when first displayed, and after it's been clicked once.
var TimeInChild = React.createClass({
render: function() {
var t = new Date().getTime();
return (
<p>Time in child:{t}</p>
);
}
});
var Main = React.createClass({
onTest: function() {
this.setState({'test':'me'});
},
shouldComponentUpdate: function(nextProps, nextState) {
if (this.state == null)
return true;
if (this.state.test == nextState.test)
return false;
return true;
},
render: function() {
var currentTime = new Date().getTime();
return (
<div onClick={this.onTest}>
<p>Time in main:{currentTime}</p>
<p>Click me to update time</p>
<TimeInChild/>
</div>
);
}
});
ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>
It seems that the accepted answers are no longer the case when using React hooks (with primitive values, see comments on this answer for details). You can see in this code sandbox that the class component is rerendered when the state is set to the same value, while in the function component, setting the state to the same value doesn't cause a rerender.
https://codesandbox.io/s/still-wave-wouk2?file=/src/App.js
React 18 and beyond
Starting from React 18 all state updates are automatically batched. In this way, React groups multiple state updates into a single re-render for better performance.
So when you update your state, React always try to batch these updates in a group update, causing fewer render than setState calls. The behaviour is the same when using hooks.
You can read the very long explanation in the Automatic batching for React 18 announcement.
React 17 and below
In React 17 and below, only updates inside React event handlers are batched. Updates triggered from promises, setTimeout, native event handlers, or other events are not batched in React by default.
Another reason for "lost update" can be the next:
If the static getDerivedStateFromProps is defined then it is rerun in every update process according to official documentation https://reactjs.org/docs/react-component.html#updating.
so if that state value comes from props at the beginning it is overwrite in every update.
If it is the problem then U can avoid setting the state during update, you should check the state parameter value like this
static getDerivedStateFromProps(props: TimeCorrectionProps, state: TimeCorrectionState): TimeCorrectionState {
return state ? state : {disable: false, timeCorrection: props.timeCorrection};
}
Another solution is add a initialized property to state, and set it up in the first time (if the state is initialized to non null value.)
Not All Components.
the state in component looks like the source of the waterfall of state of the whole APP.
So the change happens from where the setState called. The tree of renders then get called from there. If you've used pure component, the render will be skipped.
Regardless of the well explained answers here, there may be other reasons why you don't see the change you expect post changing the props or state:
Watch out for any event.preventDefault(); in the code where you want to re-render by a state \ props change, as it will cancel any cancelable event following this statement.
You could use setState() only after comparing the current state value and the new one and they are different.

What is determining the order in which these components' 'mount' functions execute?

In my code, app is the highest (functional) component. It renders piggybank (which is a class component) which renders piggychild (which is a functional component).
Each component 'console.logs' the name of itself (in the render method for class-component; simply in the function for a functional component) and has a 'mount' function (for the class-component, this means a componentDidMount method; for functional component, this means a useEffect hook whose callback's 2nd parameter is an empty array) which logs 'mount' to the console.
As you can see in the picture, PiggyBank's mount fires first, then PiggyChild's, then App's.
Is there a rule in React that governs the order in which components' 'mount' functions occur?
Or is it enough to just know that mount functions occur after all components are rendered?
useEffect has a different timing than componentDidMount. The hook that's closest to CDM is actually useLayoutEffect.
Here's a bit from ReactTraining's blog on useEffect:
They run at different times
First, let's talk about the timing of each. componentDidMount runs
after the component mounts. As the docs say, if you set state
immediately (synchronously) then React knows how to trigger an extra
render and use the second render's response as the initial UI so the
user doesn't see a flicker. Imagine you need to read the width of a
DOM element with componentDidMount and want to update state to reflect
something about the width. Imagine this sequence of events:
Component renders for the first time.
The return value of render() is used to mount new DOM.
componentDidMount fires and sets state immediately (not in an async
callback)
The state change means render() is called again and returns new JSX
which replaces the previous render.
The browser only shows the second render to avoid flicker.
It's nice that this is how it works for when we need it. But most the
time we don't need this pre-optimized approach because we're doing
asynchronous network calls and then setting state after the paint to
the screen.
componentDidMount and useEffect run after the mount. However useEffect
runs after the paint has been committed to the screen as opposed to
before. This means you would get a flicker if you needed to read from
the DOM, then synchronously set state to make new UI.
How do get the old behavior back when we need it?
useLayoutEffect was designed to have the same timing as
componentDidMount. So useLayoutEffect(fn, []) is a much closer match
to componentDidMount() than useEffect(fn, []) -- at least from a
timing standpoint.
Does that mean we should be using useLayoutEffect instead?
Probably not.
If you do want to avoid that flicker by synchronously setting state,
then use useLayoutEffect. But since those are rare cases, you'll want
to use useEffect most of the time.
I've added an example which has both Layout Effects and normal Effects in the scenario you described:
The results of running this are as follows. The main thing to note with this is that The Layout effects happen in a sensible order with the componentDidMount lifecycle hook, whereas the effects happen later.
App
PiggyBank
PiggyChild
PiggyChild LayoutEffect
PiggyBank Mount
App LayoutEffect
PiggyChild Effect
App Effect
const {useEffect, useLayoutEffect} = React;
function App(){
console.log('App');
useLayoutEffect(()=>console.log('App LayoutEffect'),[]);
useEffect(()=>console.log('App Effect'),[]);
return <PiggyBank/>;
}
class PiggyBank extends React.Component{
componentDidMount(){
console.log('PiggyBank Mount');
}
render(){
console.log('PiggyBank');
return <PiggyChild/>
}
}
function PiggyChild(){
console.log('PiggyChild');
useLayoutEffect(()=>console.log('PiggyChild LayoutEffect'),[]);
useEffect(()=>console.log('PiggyChild Effect'),[]);
return <div/>;
}
ReactDOM.render(<App/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
Without the LayoutEffects, the order will be:
The console.logs in the render functions, in order from parent to child, since part of rendering each component is rendering the children
Then the componentDidMount runs immediately after it finishes mounting. If there are multiple components with componentDidMounts or useLayoutEffects, these would be called in order from from child to parent as they finish mounting, but before the paint is committed (as mentioned above).
The useEffects run in order from child to parent as they finished mounting in that order, but they will always run after the componentDidMounts and useLayoutEffects due to the timing mentioned above.
const {useEffect} = React;
function App(){
console.log('App');
// useLayoutEffect(()=>console.log('App LayoutEffect'),[]);
useEffect(()=>console.log('App Effect'),[]);
return <PiggyBank/>;
}
class PiggyBank extends React.Component{
componentDidMount(){
console.log('PiggyBank Mount');
}
render(){
console.log('PiggyBank');
return <PiggyChild/>
}
}
function PiggyChild(){
console.log('PiggyChild');
// useLayoutEffect(()=>console.log('PiggyChild LayoutEffect'),[]);
useEffect(()=>console.log('PiggyChild Effect'),[]);
return <div/>;
}
ReactDOM.render(<App/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"/>
Actually it makes absolute sense, and order of useEffect in hierarchy is the same as in componentDidMount.
The thing is that componentDidMount fires when all children mounted (so you would see first child event, and only then parent one) - so same order as you see.
For more details about order of componentDidMount you could see here - Order of componentDidMount in React components hierarchy

Purpose of React state

This might be a really stupid question but I am writing my first project in React and am struggling to understand the purpose of setState hooks.
As far as I understand, the setState hook is used to set current values used in a component that is scoped to that component only, and does not persist if a page is reloaded for example, the value is simply held in memory until it is destroyed.
My question is, what is the difference between using setState() to store values and just simply declaring a let variable and updating it the regular way? Both methods just seem to be holding a non-persisting value scoped to that component. What is the difference?
changes in the state automatically cause your app to re-render (in most cases), so typically you store data in a state that is being displayed and possibly changed throughout the app (a menu whose options can change based on previous selections, for example).
TL;DR, even though the answer's not very long:
setState or useState is the key for React to subscribe to your component's state and update your components when necessary. Using let variables for storing app state means React will never get to know about state change and won't rerender and update your components.
A short overview of React
The core principle of React is that your components, and consequentially your UI, are a function of your app's state. When your app's state changes, components "react" to this state change and get updated. Here's a simple example:
const CounterButton = () => {
// Create a state variable for counting number of clicks
const [count, setCount] = React.useState(0);
// Decide what the component looks like, as a function of this state
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
};
ReactDOM.render(<CounterButton />, document.querySelector('#root'));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
This is a component that just creates a button that shows how many times it has been clicked. Now, the component needs to store information about how many times it has been clicked - this means that the clicks count is a part of this component's "state". That's what we get from React.useState(0) - a state variable whose initial value is 0, and a function that allows us to change the value. Whenever you call setCount with some value, React gets to know that the CounterButton component's state has changed and thus the CounterButton component needs a rerender.
So in other words, React allows you to neatly and concisely define what internal state a component requires and what the component looks like as a function of this internal state (and external props). React does rest of the work - it manages the app's state, and whenever a piece of state changes anywhere in the app, React updates the components that depend on that. In other words, components "react" to data change.
To your question
If you use a simple let variable instead of React.useState in the above example, React will no longer get to know if the count variable has changed. useState is the key for React to "subscribe" to your component's state.
const CounterButton = () => {
// React will never get to know about this variable
let count = 0;
return (
<button onClick={() => count++}>
Count: {count}
</button>
);
};
In fact, for a functional component, let variables won't work in any case because while rendering a functional component, React internally runs the function. That would mean your let variable would be reset to its default value. The above reason is more relevant to class components. Using let variables to store component state is like hiding things from React - and that's not good because then there's no one else to rerender your component when component state changes :)
This part of the React docs is a bit relevant - it does not go into any details, though.
React re-renders the new / updated state on an event occurance storing value into a variable wont trigger re-render and data is passed on form parent component to child component through props and a change in state can be reflected among all the parts.
For example we need to print 100 elements on a page if an element is modified or updated in any way this triggers re-render but using var if the variable is modified or updated this won't cause re-render where in data wont be updated.

React Hooks: useState() makes all children component re-rendered although the change doesn't affect some of them

Okay. Here is my situation. I have a big component called TheForm. In TheForm, there are the following children: TextEditor, TagInput, TipCard - this gives the advice how to write a good form and Preview:
const TheForm = () => {
const [parag1, writeParag1] = useState("");
const [parag2, writeParag2] = useState("");
const [tags, updateTags] = useState([]);
return (
<div>
<TipCard>Some suggestion here...</TipCard>
Paragraph One: <TextEditor onInput={writeParag1}/>
Paragraph One: <TextEditor onInput={writeParag2}/>
Add tags: <TagInput onInput={updateTags}/>
Preview Mode: <Preview />
<button>Submit</button>
<button>Reset</button>
</div>
);
}
The TextEditor contains a <textarea>, some buttons to format text, everytimes the value of the <textarea> changes, the proper state in TheForm is updated. Same for TagInput.
The Preview will take the state values and render them in preview mode (just like Stack Overflow ask a question).
The problem here is when one of the states is updated, it causes all the children re-rendered, even they are not using that state and even I used the React.memo for the component.
I know that when the state changes, it make the component re-rendered, so the children of the component are re-rendered too.
So how can I avoid that? I know that I can move the states down to the children, but if I do it, the Preview can not access those value. Redux can solve this problem, but is it too much to use Redux here, I mean those states are not shared with other component so using Redux is too much?
It is just how React works - it run render function every time state is changed and it cause all children to re-render too, no matter are they depended on changed state or not.
If you would like to avoid re-render of a component, you can make it pure, e.g. wrap it with React.memo. So you have to wrap each child in order to prevent it re-render.
Pure components shallow compare props to determinate if it safe to skip re-render requested from a parent. It means, you have to not only wrap children to memo but ensure the props passed to them is persistent each time of render.
Usually it means you should memoize callback like
onInput={el => updateTitle(el.target.value)} with useMemo or useCallback
and avoid, flatten or memoize objects like previewMode={{ title, description, codeBlock, tagList }} otherwise they will be re-created each time and it will invalidate shallow-compare optimisation

how does react detect changes in a component internally?

How does a component gets to know, its props are changed so that it can re-render?
Eg:
function component(props){
return <p>{this.props.childVar}</p>
}
function parentComp(){
let parentVar = 0;
setTimeout(()=>{
parentVar++;
//what happens between: after this statement is executed and till the child component is re-rendered ?
}, 1000)
return <component childVar={parentVar} />
}
One of the most important aspects of React's API is that it is declarative, and that you don't have to worry about when and how changes are being captured and handled.
Basically, each time you call render, a different tree of react elements is being made and React’s diffing algorithm will compare the old and the new tree, and will make the changes if necessary.
From a component point of view, the instance itself does not know about trees, changes and updates. After all it's just a function that returns a react element based on its paramters (props) and local state (if there is any).
You can read more about it in the Docs
In your code snippet the child component will never re-render, because what you do is just change a local variable, and child component will never know about that. So, short answer is component doesn't know is props changed.
There is two reasons for component to re-render:
this.setState was called
A parent component re-rendered

Categories

Resources