How to get React.memo functionality to work with children? - reactjs

I have a stateless functional component <Sidebar/>which simply returns its children in a div. This component is rendered by <App/> which maintains its own state, and passes an element of that state 'over the top' of Sidebar into one of its children.
const Sidebar = React.memo(({children}) => {
return <div className='sidebar'>{children}</div>
})
const App = (props) => {
const [counter, setCounter] = useState(0);
setInterval(() => {setCounter(counter => counter+1}), 1000)
return (
<Sidebar>
<ChildOne counter={counter}/>
</Sidebar>
)
}
Despite using React.memo on <Sidebar> it re-renders every time the state changes. I've done a bit of reading and experimentation and the problem seems to be that when <ChildOne/> updates its state, React is actually creating a brand new object, and so even shallow comparison shows the children as changing, thus Sidebar re-renders.
How can I get Sidebar to stop re-rendering?

From React.memo docs
React.memo only checks for prop changes. If your function component
wrapped in React.memo has a useState or useContext Hook in its
implementation, it will still rerender when state or context change.
In your case, children which is a prop, is re-rendered each time counter changes, so there's no memoization.
The only solution I see, is to not pass a state value to the sidebar children. If you need to interpolate a value only for the first time, then use refs, not state values.

Related

React useCallback() function in Parent is only working fine with Child if React.memo is used, without it not working

Parent Component
The parent component contains input which on change sets the state "local" and a button which on click takes this "local" state's value and sets it to "sendLocal"
Functions
changehandler : triggers on input type change.
sendLocalValue : takes "local" value puts it into "sendLocal" variable triggers on button click.
sendValue : this memoized function with dependeny "sendLocal" is passed on as a prop in child component triggers once the child is rendered.
import React, { useState, useCallback } from "react";
import ChildComponent from "./ChildComponent";
function ParentComponent() {
const [local, setLocal] = useState();
const [sendLocal, setsendLocal] = useState();
const changehandler = (e) => {
console.log("parent");
setLocal(e.target.value);
};
const sendLocalValue = () => {
setsendLocal(local);
};
const sendValue = useCallback(() => {
return sendLocal;
}, [sendLocal]);
return (
<>
<input type="text" onChange={changehandler} defaultValue={local} />
<button onClick={sendLocalValue}>send</button>
<ChildComponent getValue={sendValue} />
</>
);
}
export default ParentComponent;
Child Component
getValue prop calls the memoized "sendValue" function of parent which returns the value of sendLocal.
Problem
Everything works fine,the child component renders only when the "sendLocal" value changes on button click but if i remove React.memo() in child both the component render on input type change even with useCallback() used, why?
import React, { useEffect, useState } from "react";
function ChildComponent({ getValue }) {
console.log("child");
return <div>child's {getValue()}</div>;
}
export default React.memo(ChildComponent);
There is a general misconception that React components rerender when props or state change. while this is partially true, can lead to misunderstoods: a react component rerenders when its state changes or when its parent rerenders (because its state changed or because its parent rerendered, and so on).
So this means that every time ParentComponent rerenders, all its (non memoized) children will rerender.
To avoid this, you can use React.memo or React.PureComponent.
You can verify that by removing React.memo and not passing any props to the ChildComponent. Even with no props, it will rerender every time its parent rerenders.
when the parent re-renders, the whole props object for the child is created as a new object, which leads to the child re-render.
it does not matter that you have memoized all the different individual props, you have to memoize the component itself so that it does not re-render when the whole props object changes.
In this case, it also means that you still have to memoize the function as well as the child component, because React.memo only does a shallow comparison of individual props.

Preventing setState rerenders entire functional component

In a class component when the state or props was changed the render method will execute, but I don't know in a functional component when the same happens which part of the code is rerendered?
If you have some expensive calculation inside your component that you want to skip, you can use the useMemo hook. It will do the calculation the first time, and then on subesequent times it will only recalculate if one of the dependencies change. For example:
import React, { useMemo } from 'react';
const Example = ({ people }) => {
const [ageFilter, ageFilter] = useState(10);
const filteredPeople = useMemo({
return people.filter(person => person.age >= ageFilter);
}, [people, ageFilter]);
return (
<div>
{filteredList.map(person=> (
// some components
))}
</div>
)
}
If your function component renders the same result given the same props, you can use React.memo. Similarly for class component React provides PureComponent.
It is mentioned in React doc:
If your function component renders the same result given the same
props, you can wrap it in a call to React.memo for a performance boost
in some cases by memoizing the result. This means that React will skip
rendering the component, and reuse the last rendered result.
So, you need to use React.memo.

Does React renders Component due to props?

According to that link: http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
render() may be triggered with new props. Could someone give me a code example for that? I cannot see how props change invoke rendering! Please not by changing the props via the state; then it is setState() that invokes render()...
Look at shouldComponentUpdate() - this is it's signature - it returns a boolean. Props is there so you can compare and manually say whether the component should update.
shouldComponentUpdate(nextProps, nextState)
For function components React.memo is the replacement for shouldComponentUpdate which is used with class components.
const myComponent = React.memo(props => {
...
code of React.FunctionComponent
...
},
(prevProps, nextProps) => prevProps.propA === nextProps.propA
);
React.memo gets two arguments shown above: a React.FunctionComponent which will be wrapped around by memo and an optional function that returns a boolean.
When the function returns true, the component will not be re-rendered. If the function is omitted then its default implementation in React.memo works like the implementation of shouldComponentUpdate in React.PureComponent. E.g. it does shallow comparison of props, the difference is that only props are taken into account because state doesn’t exist for functional components.
Using hooks is neater to show. The new props data passed to ComponentB causes a re-rendering of ComponentB:
import React, { useState } from 'react'
import ComponentB from '...'
const ComponentA = props => {
const [data, setData] = useState(0) // data = 0
handleChangeProp = item => setData(item) // data = 1
return(
<div>
<button onClick{() => handleChangeProp(1)}
<ComponentB props={data} />
</div>
)
}
Yes, when you do a setState(newState) or when you pass in changed props the component will re render, this is why you can't mutate. The following will not work because you set state with mutated state.
export default function Parent() {
const [c, setC] = useState({ c: 0 });
console.log('in render:', c);
return (
<div>
<button
onClick={() =>
setC(state => {
state.c++;
console.log('state is:', state);
return state;
})
}
>
+
</button>
<Child c={c.c} />
</div>
);
}
That code "won't work" because pressing + will not cause a re render, you mutated state and then set state with the same object reference so React doesn't know you changed anything.
This is how React detects changes, you may think that comparing {c:0} to {c:1} is a change but because you mutated there actually is no change:
const a = {c:1};
a.c++;//you "changed" a but a still is a
To indicate a change in React you have to create a new reference:
const a = {c:1};
const b = {...a};//b is shallow copy of a
a===b;//this is false, even though both a and b have same internal values
This means you can also have unintended renders because you create an object prop that may have the same value but still is a different reference than the last time you created it.
Note that even <Child prop={1} will cause Child to render if Child is not a pure component (see links at the end).
What you want to avoid is doing <Child prop={{c:value}} because every time you pass prop it'll force Child to render and React to do a virtual DOM compare even if value didn't change. The virtual DOM compare will probably still detect that Child virtual DOM is the same as last time and won't do an actual DOM update.
The most expensive thing you can do is <Child onEvent={()=>someAction(value)}. This is because now the virtual DOM compare will fail even if value and someAction did't change. That's because you create a new function every time.
Usually you want to memoize creating props in a container, here is an example of doing this with react-redux hooks. Here is an example with stateful components passing handlers.

Why is my component rendering when useState is called with the same state?

I have a simple functional component with a boolean state. And buttons to change the state.
It is initially set to true so when I press the true-button, it does NOT render.
But if I press the false-button, it re-renders AND if I press false-button AGAIN, it will re-render even though the state is already set to false..
Could someone explain why the component re-renders when the state changes to the exact same state? How to prevent it from re-rendering?
import React, {useState} from 'react';
const TestHooks = () => {
const [state, setState] = useState(true);
console.log("rendering..", state);
return(
<div>
<h1>{state.toString()}</h1>
<button onClick={() => setState(true)}>true</button>
<button onClick={() => setState(false)}>false</button>
</div>
)
}
export default TestHooks;
From the react docs :
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.
So your component will not re-render every time setState is called with the same state. It will re-render only 2 times and then bail out. That's just how react works and that should not be a concern except if you're doing some heavy calculations in the render method.
It doesn't matter if the state value is already the same as the one you are trying to update it with. The fact is, setState() will re-render your component regardless, that's just what React does to ensure all changes (if any) are reflected in the UI.
If you want to avoid unnecessary re-renders, use an intermediary function to check if the state values are already equal before updating it.
import React, {useState} from 'react';
const TestHooks = () => {
const [state, setState] = useState(true);
const updateState = (boolean) => {
if(boolean !== state){
setState(boolean)
}
}
console.log("rendering..", state);
return(
<div>
<h1>{state.toString()}</h1>
<button onClick={() => updateState(true)}>true</button>
<button onClick={() => updateState(false)}>false</button>
</div>
)
}
export default TestHooks;
render() will be called whenever setState() is called. That is the reason why we have the concept of PureComponent in React. Read https://reactjs.org/docs/react-api.html#reactpurecomponent

How do I force a child component to rerender when given new props values?

I have tried this pattern.
ParentComponent
...
render(
return <ChildComponent newProps="newPropsValue />)
ChildComponent
...
ComponentWillReceiveProps{
this.setState({"propsKey": "newPropsValue"})
}
As far as I understand the initial component rendering is triggered by the props change, and as setState is asynchronous (for some reason), the rendering with the new state update is not done on the first pass.
However what I don't understand is why when it finally decides to update the state, it doesn't rerender the component. I thought state changes that are caused by setState always trigger a rerender.
So in the end I have a component that uselessly rerenders before the state is actually changed, and then does nothing when/if(?) the state is updated. I don't understand this behaviour at all.
setState will trigger componentUdpate -> componentWillUpdate -> render. props change will trigger componentWillReceiveProps before this chain. You can have a look here at this image about React lifecycle. You can see the different how React behave on props and state.
So:
However what I don't understand is why when it finally decides to update the state, it doesn't re-render the component.
Updating state by setState will trigger the render function (re-render). And props also trigger render as well.
Following your code:
componentWillReceiveProps:
this.props.newProps="newPropsValue"
this.state.propsKey="newPropsValue"
render: as above, nothing change.
If any event of childComponent setting propsKey by setState (onClick, onChange ...). Assuming setState({propsKey: "anotherValue"}). Then render will be triggered again with this.state.propsKey="anotherValue and this.props.newProps="newPropsValue"
Now let's update your childComponent's props within parentComponent, assuming newProps="latestPropsValue":
Before componentWillReceiveProps:
this.props.newProps="latestPropsValue"
this.state.propsKey="anotherValue"
After componentWillReceiveProps:
this.props.newProps="latestPropsValue"
this.state.propsKey="latestPropsValue"
How do I force a child component to rerender when given new props values?
If your render is using state then setState inside render. And if you are using props inside render, it also being updated accordingly
I have found a nice solution using key attribute. If we changed key property of a child component or some portion of React Component, it will re-render entirely. It will use when you need to re-render some portion of React Component or re-render a child component depending on props or state. Here is a example. I will re-render the full component.
import React, { useState, useEffect } from "react";
import { PrEditInput } from "./shared";
const BucketInput = ({ bucketPrice = [], handleBucketsUpdate, mood }) => {
const data = Array.isArray(bucketPrice) ? bucketPrice : [];
const [state, setState] = useState(Date.now());
useEffect(() => {
setState(Date.now());
}, [mood, bucketPrice]);
return (
<span key={state}>
{data.map((item) => (
<PrEditInput
key={item.id}
label={item?.bucket?.name}
name={item.bucketId}
defaultValue={item.price}
onChange={handleBucketsUpdate}
mood={mood}
/>
))}
</span>
);
};
export default BucketInput;

Resources