Avoid re-render with `useCallback` - javascript

I am trying to figure out why when I click on a specific component its sibling it will render too
function CountButton({increment, count, number}) {
console.log(`Render CountButton ${number}`)
return <button onClick={() => increment(count + 1)}>{count}</button>
}
function DualCounter() {
const [count1, setCount1] = React.useState(0)
const increment1 = React.useCallback(() => setCount1(c => c + 1), [])
const [count2, setCount2] = React.useState(0)
const increment2 = React.useCallback(() => setCount2(c => c + 1), [])
console.log('Render DualCounter')
return (
<>
<CountButton count={count1} increment={increment1} number={1} />
<CountButton count={count2} increment={increment2} number={2} />
</>
)
}
I use useCallback and I pass theses function to use avoid that in any render the functions reference will be a different reference.

You are seeing a re-render on the sibling <CountButton /> component, because each time you hit the button to update the counter, you are actually updating a state value in the parent component <DualCounter />, which causes a re-render on that component as well.
And since DualCounter is re-rendered, child components will re-render as well, which in this case includes both <CountButton /> elements.
A solution to prevent this, would be wrapping CountButton component with React.memo(). This will prevent a re-render on a component that didn't have any change on the props values.
Example below:
function CountButton({increment, count, number}) {
console.log(`Render CountButton ${number}`)
return <button onClick={() => increment(count + 1)}>{count}</button>
}
const CountButtonMemo = React.memo(CountButton)
function DualCounter() {
const [count1, setCount1] = React.useState(0)
const increment1 = React.useCallback(() => setCount1(c => c + 1), [])
const [count2, setCount2] = React.useState(0)
const increment2 = React.useCallback(() => setCount2(c => c + 1), [])
console.log('Render DualCounter')
return (
<>
<CountButtonMemo count={count1} increment={increment1} number={1} />
<CountButtonMemo count={count2} increment={increment2} number={2} />
</>
)
Another solution would be not updating the DualCounter state on each change caused by events on your CountButton components, which will stop triggering unwanted re-renders on their siblings. You could handle the state directly on each CountButton component if this made sense for your app.
Alternatively, you could use a React state management tool, such as Redux, which also solves exactly this issue, by taking charge of delegating the state of your app separated from your components themselves.

Related

React's Context API re-renders all components that are wrapped inside the Context

This is a very common performance problem while using the Context API. Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown.
If I have a the wrapper as this:
<CounterProvider>
<SayHello />
<ShowResult />
<IncrementCounter />
<DecrementCounter />
</CounterProvider>
And the value props as:
<CounterContext.Provider value={{increment, decrement, counter, hello }} >
{children}
</CounterContext.Provider>
Everytime I increment the count value from the IncrementCounter component, the entire set of wrapped components re-renders as it is how the Context API is supposed to work.
I did a bit of research and came across these solutions:
Split the Context into N number of Context according to the use-case : This solution works as expected.
Wrap the value provider using React.Memo: I saw a lot of articles suggesting to the React.Memo API as follows:
<CounterContext.Provider
value={useMemo(
() => ({ increment, decrement, counter, hello }),
[increment, decrement, counter, hello]
)}
>
{children}
</CounterContext.Provider>
This however doesn't work as expected. I still can see all the components getting re-rendered. What I'm doing wrong while using the Memo API? Dan Abramov does recommend to go by this approach in an open React issue
If anyone can help me out on this one. Thanks for reading.
"Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown."
The above statement is true if a context is used like in the below example where components are directly nested in the provider. All of them re-render when count changes, no matter wether they are called useContext(counterContext) or not.
const counterContext = React.createContext();
const CounterContextProvider = () => {
const [count, setCount] = React.useState(0);
return (
<counterContext.Provider value={{ count, setCount }}>
<button onClick={() => setCount((prev) => prev + 1)}>Change state</button>
<ComponentOne/>
<ComponentTwo />
</counterContext.Provider>
);
};
const ComponentOne = () => {
console.log("ComponentOne renders");
return <div></div>;
};
const ComponentTwo = () => {
console.log("ComponentTwo renders ");
return <div></div>;
};
function App() {
return (
<CounterContextProvider/>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<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>
"Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown."
The statement is false if you are consuming nested components with children. This time when count changes CounterContextProvider renders, but since it's rendering because its state has changed and not because of its parent rendering, and because a component cannot mutate its props, React won't render children. That's it if it was a normal component.
But since there is a context involved here, React will find all components that contain useContext(counterContext) and render them.
const counterContext = React.createContext();
const CounterContextProvider = ({ children }) => {
const [count, setCount] = React.useState(0);
return (
<counterContext.Provider value={{ count, setCount }}>
<button onClick={() => setCount((prev) => prev + 1)}>Change state</button>
{children}
</counterContext.Provider>
);
};
const ComponentOne = () => {
const { count } = React.useContext(counterContext);
console.log("ComponentOne renders");
return <div></div>;
};
const ComponentTwo = () => {
console.log("ComponentTwo renders ");
return <div></div>;
};
function App() {
return (
<CounterContextProvider>
<ComponentOne />
<ComponentTwo />
</CounterContextProvider>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<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>
In the above example only ComponentOne renders when count changes, which is normal cause he is consuming it. Every component that calls useContext(counterContext) renders if one value of the context changes.
Even with useMemo wrapping the context object as you did, that's the behavior you get as soon as one variable in its dependency array changes.

React - useCallback vs useMemo for JSX elements

I have implemented this component:
function CardList({
data = [],
isLoading = false,
ListHeaderComponent,
ListEmptyComponent,
...props
}) {
const keyExtractor = useCallback(({ id }) => id, []);
const renderItem = useCallback(
({ item, index }) => (
<Card
data={item}
onLayout={(event) => {
itemHeights.current[index] = event.nativeEvent.layout.height;
}}
/>
),
[]
);
const renderFooter = useCallback(() => {
if (!isLoading) return null;
return (
<View style={globalStyles.listFooter}>
<Loading />
</View>
);
}, [isLoading]);
return (
<FlatList
{...props}
data={data}
keyExtractor={keyExtractor}
renderItem={renderItem}
ListHeaderComponent={ListHeaderComponent}
ListFooterComponent={renderFooter()}
ListEmptyComponent={ListEmptyComponent}
/>
);
}
As my CardList component is heavy, I have tried to optimize it following these tips.
But, I think that instead of using useCallback for renderFooter, I should use useMemo, in order to memoize the resulted JSX and not the method:
const ListFooterComponent = useMemo(() => {
if (!isLoading) return null;
return (
<View style={globalStyles.listFooter}>
<Loading />
</View>
);
}, [isLoading]);
Am I correct?
If you want to avoid expensive calculations use useMemo. useCallback is for memoizing a callback/function.
Official docs do have an example of using useMemo with JSX for avoiding re render:
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
Personal observation:
But to be more detailed, it seems it is not that useMemo here magically prevents re render, but the fact that you render same reference on the same spot in component hierarchy, makes react skip re render.
Here:
let Child = (props) => {
console.log('rendered', props.name);
return <div>Child</div>;
};
export default function Parent() {
let [a, setA] = React.useState(0);
let [b, setB] = React.useState(0);
// Only re-rendered if `a` changes:
const child1 = React.useMemo(() => <Child a={a} name="a" />, [a]);
// Only re-rendered if `b` changes:
const child2 = React.useMemo(() => <Child b={b} name="b" />, [b]);
return (
<div
onClick={() => {
setA(a + 1);
}}
>
{a % 2 == 0 ? child2 : child1}
</div>
);
}
If you keep clicking the div you can see it still prints "rendered b" even though we never changed the b prop. This is because react is receiving a different reference each time inside the div, and doesn't optimize the re render - like it does if you had simply rendered only child2 without that condition and child1.
Note: The fact that react skips rendering when it receives same element reference in the same spot is known, so apparently useMemo technique works because of that when it comes to rendering optimization.
"Should" is a matter of opinion, but you certainly can. React elements are fully reusable. It's not likely to make any real difference to your component, though, since creating the elements is fast and renderFooter is just used immediately in a component that's already running (unlike keyExtractor and renderItem, which you're passing to FlatList, so you want to make them stable where possible so FlatList can optimize its re-rendering). But you certainly can do that.
useMemo is perfectly sensible here as it memoizes the result (the JSX). As you say, useCallback memoizes the function not the result.

Way to render a new component onClick in react js

Am trying to render a new component onclick a button in react js. Am using functional components and I can't handle it. Eg: am in the UserManagement component and on a button click I need to render another component named employee management.
You can conditionally render your component.
Example :
EmployeeManagement.js
const EmployeeManagement = () => {
....
return (
<div>
EmployeeManagement
</div>
);
}
UserManagement.js
const UserManagement = () => {
const [hasRender, setRender] = useState(false);
const onShow = React.useCallback(() => setRender(true), []);
return (
<>
<button onClick={onShow}>Show Employee Management</button>
{hasRender && <EmployeeManagement />}
</>
)
}
One way to do this would be to add a local state in UserManagement,
that holds a boolean value indication whether the component should be hidden or shown.
Then you will have something like:
function UserManagement() {
const [compIsShown, setCompIsShown] = useState(false);
return (
// Whatever else you're rendering.
<button onClick={() => setCompIsShown(true)}>...</button>
{compIsShown && <OtherComp />}
)
}
What will happen is that compIsShown will initialize as false,
so this condition compIsShown && <OtherComp /> will prevent it from rendering.
Then, when you click the button, the state will set, causing a re-render, except now the condition will be true, so <OtherComp> will be rendered.
There are other ways to go about this.
Depends mostly on the use-case.
use a visible state & toggle it in onClick:
const [visible, setVisible] = useState(false)
onClick = () => {setVisible(true)}
then render it like this:
{visible && <EmployeeManagement onClick={onClick} />}

export Hooks in React for Nested Components?

I'm exporting hooks with nested components so that the parent can toggle state of a child. How can I make this toggle work with hooks instead of classic classes or old school functions?
Child Component
export let visible;
export let setVisible = () => {};
export const ToggleSwitch = () => {
const [visible, setVisibile] = useState(false);
return visible && (
<MyComponent />
)
}
Parent
import * as ToggleSwitch from "ToggleSwitch";
export const Parent: React.FC<props> = (props) => {
return (
<div>
<button onClick={() => ToggleSwitch.setVisible(true)} />
</div>
)
}
Error: Linter says [setVisible] is unused variable in the child... (but required in the parent)
You can move visible state to parent like this:
const Child = ({ visible }) => {
return visible && <h2>Child</h2>;
};
const Parent = () => {
const [visible, setVisible] = React.useState(false);
return (
<div>
<h1>Parent</h1>
<Child visible={visible} />
<button onClick={() => setVisible(visible => !visible)}>
Toggle
</button>
</div>
);
};
If you have many child-components you should make more complex logic in setVisible. Put object to useState where properties of that object will be all names(Ids) of child-components
as you know React is one-way data binding so if you wanna pass any props or state you have only one way to do that by passing it from parent to child component and if the logic becomes bigger you have to make it as a global state by using state management library or context API with react hooks use reducer and use effect.

React Hooks render twice

I define a scene: we have a component that uses parent's props and itself state.
There are two Components DC and JOKER and my step under the below:
click DC's button
DC setCount
JOKER will render with the old state
running useEffect and setCount
JOKER does render again
I want to ask why JOKER render twice(step 3 and 5) and the first render squanders the performance. I just do not want step 3. If in class component I can use componentShouldUpdate to avoid it. But Hooks has the same something?
My code under the below, or open this website https://jsfiddle.net/stephenkingsley/sw5qnjg7/
import React, { PureComponent, useState, useEffect, } from 'react';
function JOKER(props) {
const [count, setCount] = useState(props.count);
useEffect(() => {
console.log('I am JOKER\'s useEffect--->', props.count);
setCount(props.count);
}, [props.count]);
console.log('I am JOKER\'s render-->', count);
return (
<div>
<p style={{ color: 'red' }}>JOKER: You clicked {count} times</p>
</div>
);
}
function DC() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => {
console.log('\n');
setCount(count + 1);
}}>
Click me
</button>
<JOKER count={count} />
</div>
);
}
ReactDOM.render(<DC />, document.querySelector("#app"))
It's an intentional feature of the StrictMode. This only happens in
development, and helps find accidental side effects put into the
render phase. We only do this for components with Hooks because those
are more likely to accidentally have side effects in the wrong place.
-- gaearon commented on Mar 9, 2019
You can simply make modifications in ./index.js
change this
<React.StrictMode>
<App />
</React.StrictMode>
to this
<>
<App />
</>
React.StrictMode causes component render in development mode. (works in reactjs version 18.0.2)
I'm not sure I understand your question, but here goes.
When your <DC /> component changes state, it passes the new state value count to the component Joker. At this point the component will rerender, accounting for the first change.
Then you bind the effect to props.count changes;
useEffect(() => {
console.log('I am JOKER\'s useEffect--->', props.count);
setCount(props.count);
}, [props.count]);// <-- This one
Which triggers when the component gets the new value from the component DC. It will set the state of it self Joker to props.count, which causes the component to rerender.
Which then gives you the following output:
I am JOKER's render--> 1 // Initial render where Joker receives props from DC
index.js:27 I am JOKER's useEffect---> 2 // The hook runs because props.count changed
index.js:27 I am JOKER's render--> 2 // Joker rerenders because its state updated.
If we just want to do the same something alike componentShouldUpdate, we can use useMemo.
function DC() {
const [count, setCount] = useState(0);
const [sum, setSum] = useState(0);
const memoizedJOKER = useMemo(() => <JOKER count={count} />, [count]);
return (
<div>
<button onClick={() => {
// setCount(count + 1);
setSum(sum + 1);
console.log('---click---');
console.log('\n');
}}>
Click me
</button>
<p>DC: You clicked {count} times</p>
<p>now this is {sum} times</p>
{memoizedJOKER}
</div>
);
}
When you click button, JOKER does not render again.
Use the following custom useEffect code to force react to render a component once, all you need to do is import and use it in place of usEffect.
import {useRef} from 'react'
export const useEffectOnce = ( effect )=> {
const destroyFunc = useRef();
const effectCalled = useRef(false);
const renderAfterCalled = useRef(false);
const [val, setVal] = useState(0);
if (effectCalled.current) {
renderAfterCalled.current = true;
}
useEffect( ()=> {
// only execute the effect first time around
if (!effectCalled.current) {
destroyFunc.current = effect();
effectCalled.current = true;
}
// this forces one render after the effect is run
setVal(val => val + 1);
return ()=> {
// if the comp didn't render since the useEffect was called,
// we know it's the dummy React cycle
if (!renderAfterCalled.current) { return; }
if (destroyFunc.current) { destroyFunc.current(); }
};
}, []);
};
For more information click here

Categories