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.
Related
How can I make a Hook state change to re-render only the render of the Hook and not the component? I went around this by adding logic to the null returning middleware component, but I don't know if it's a good idea.
import { useState, useMemo, useEffect } from "react";
import "./styles.css";
function useTime() {
const [a, setA] = useState(0);
console.log("good-render", a);
useEffect(() => {
setInterval(() => {
setA(a + 1);
}, 3000);
}, []);
}
function Aaa() {
useTime();
return <></>;
}
export default function App() {
console.log("bad-render");
return (
<div className="App">
<Aaa />
</div>
);
}
Insofar as I'm aware, you can't stop a component rendering from a hook. Whenever the state in a component changes, the component gets set to re-render, which calls all hooks in it. In fact, conditionally calling hooks would break the rules of hooks.
Your best bet would be to encapsulate all the logic you need in refs and effects if you don't want the custom hook to cause a re-render. That being said, unless you have other issues with your code (i.e. a lot of really complicated and time consuming code), then a few extra re-renders isn't the worst thing in the world.
There's definitely a point to the idea that premature optimization is a problem.
That being said, different approaches for preventing the entire tree from re-rendering would be:
Encapsulate the logic in a separate component without children (as you show above)
Use React.memo on the components that make up the children so
React can know it should check for changes by reference on all the
props to determine whether to re-render.
It's really difficult for me to decide whether to use useCallback or not.
If there isn't an expensive function, should I just omit it?
(But I don't know whether a function is expensive or not...)
Or when a component is re-rendered frequently, I could wrap every function in it by useCallback?
Any idea?
You can consider following use case regarding using useCallback
React's useCallback Hook can be used to optimize the rendering behavior of your React function components.
Usually useCallback is very helpful during passing callback props to child components.
Let's say if a child component that accepts a callback relies on a referential equality check to prevent unnecessary re-renders when its props change, then it is important that any callback props do not change between renders.
To do this, the parent component can wrap the callback prop in useCallback and be guaranteed that it is passing the same function object down into the optimised child component.
Let's say you have a component that renders a big list of items.
import React from 'react';
import useSearch from './fetch-items';
function ListItem({ value, handleClick }) {
const items = useSearch(value);
const itemToElement = item => <div onClick={handleClick}>{item}</div>;
return <div>{items.map(itemToElement)}</div>;
}
export default React.memo(ListItem);
Here, ListItem renders a list of items. Let's imagine the list could be big, probably a few thousands of items. To preserve the list re-rendering, you wrap it into React.memo.
The parent component of ListItem needs provides a handler function when an item is clicked.
import React, { useCallback } from 'react';
export default function ParentComponent({ value }) {
const handleClick = useCallback(item => {
console.log('List item is clicked', item);
}, [value]);
return (
<ListItem
value={value}
handleClick={handleClick}
/>
);
}
handleClick callback is memoizied by useCallback(). As long as term variable stays the same, useCallback() returns the same function instance.
Even if for some reason ParentComponent component re-renders, handleClick stays the same and doesn’t break the memoization of ListItem.
Notes: Please don't mix React's useCallback Hook with React's memo API. useCallback is used to memoize functions whereas React memo is used to wrap React components to prevent re-renderings. They provide two different functionality.
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.
I know the gist of converting React Class components to functional components, but I found an instance where I nor my searching on the internet know the answer.
export default class Counter extends Component<Props, State> {
count = 0
updateCount = ()=> this.count +=1
render() {
return <div onClick={this.updateCount}>{this.count}</div>
}
}
Ignore the ugliness of the class, but how would i carry the count over into a functional component, with useRef?
The reason I ask is because in another class i am trying to convert, I have an async PromisePool running, that updates the downloaded variable each time a promise finishes, and when I tried to put downloaded into the state, it would always rerender the component and lose the data.
With useRef, you can create a variable which is not initialized on each re-render. The above component would look like
export default () => {
const count = useRef(0);
const updateCount = ()=> count.current +=1
render() {
return <div onClick={updateCount}>{count}</div>
}
}
However, you must know that updating a ref doesn't cause a re-render and hence updated value won't reflect in render
If you wish to trigger a re-render, make use of useState instead
export default () => {
const [count, setCount] = useState(0);
const updateCount = ()=> setCount(prevCount => prevCount + 1);
render() {
return <div onClick={updateCount}>{count}</div>
}
}
This is a direct example of moving the component over to a functional component. The count will remain in use as long as the component doesn't fully remount. It will remain even after re-renders.
export default function Counter(){
const [count,setCount] = useState(0);
return <div onClick={()=>setCount(count=>count+1)}>{count}</div>
}
Unless you are remounting this component by using a different key or changing the dom above it (i.e. adding a wrapping div after your promise finishes), then this should work fine (but in the instance of remounting, the class component would also reset it's counter).
You cannot carry the updated value of count with useRef since no rerender occurs when count is updated. useRef is for persisting an object in a component over multiple renders.
A possible solution for your PromisePools issue: Instead of converting the parent into a functional component, make use of the shouldComponentUpdate() lifecycle method in your class component to prevent a rerender of the child component when the state is changed.
Take a look at the lifecycle docs for more info:
https://reactjs.org/docs/react-component.html#shouldcomponentupdate
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.