When sould I use useCallback in React? - reactjs

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.

Related

React - re-rendering of the component via hook

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.

Dispatching actions inside inner functions of funtional component

I read that inner functions in statefull functional component should be defined using useCallback when we use setState or that function is passed as prop to child component. But what about dispatching actions? Do we need to use 'useCallback' there also?
import React from "react";
import { logout } from "../../../../actions/auth";
import { useDispatch } from "react-redux";
function Navbar (props) {
...
const dispatch = useDispatch();
const handleClick = () => {
dispatch(logout());
}
return (
<div>
<button onClick={handleClick}>Logout</button>
</div>
)
}
So first of all you need to know why you sometimes need useCallback around your functions and listeners.
The powerful useMemo and useCallback hooks create a value which is referentially stable (prev === current) between re-renders under the condition that the inputs (or “dependencies”) arrays doesn’t change its values (again, referentially). This allows children components you pass your values to to memoize themselves with React.memo and similar tools.
Why would you need to let them memoize? For performance reasons. And the first rule of performance optimization is you don’t optimize prematurely. So, do not blindly wrap everything in useCallback and useMemo: write your code and only once you reach some performance bottleneck investigate and eventually solve with those hooks.
To answer your question, do dispatch functions need to be wrapped? As I infer from your code we are talking about React Redux’ dispatch. The answer is no: React Redux dispatch, useReducer’s dispatch, useState’s updater function are all referentially stable.
dispatch does not effect to your inner functions. Your inner functions will be re-created when you create it without useCallback or with useCallback but dependencies changed
You can put dispatch as a dependency of useCallback and use it like normal function
const handleClick = useCallback(() => {
dispatch(logout());
}, [dispatch])

How to force an update for a functional component?

I'm learning redux and want to find out how useSelector updates a component, because the component doesn't have its own state.
I understand that useSelector() subscribes the component to the store, and when the store is updated, the component also updates.
Class components have this.forceUpdate(), but functional components don't have it.
How does one force-update a functional component?
You can simply do this
Add a dummy state that you can change to reliably initiate a re-render.
const [rerender, setRerender] = useState(false);
...
//And whenever you want to re-render, you can do this
setRerender(!rerender);
And this will re-render the component, since components always re-render on state change
The react-redux package relies on the rendering engine of react/react-dom to trigger the re-render of a given component that uses the useSelector hook.
If you take a look at the source of useSelector you can notice the use of useReducer:
const [, forceRender] = useReducer((s) => s + 1, 0)
As the name (forceRender) implies, redux uses this to trigger a re-render by react.
With v8 of react-redux the implementation of this mechanism changes but still relies on react-hooks for the re-render.
If you are curious how React handles re-renders, take a look at this excellent SO answer. It provides a great entry on the implementation details of how react-hooks are associated with the calling component.
I don't repeat Ryan here, but to sum it up:
The renderer keeps a reference to the component that is currently rendered. All hooks being executed during this render (no matter how deeply nested in custom-hooks they are) ultimately belong to this component.
So, the useReducer is associated with the component within which you called useSelector.
The dispatch function of useReducer triggers a re-render of this component (React either calls the render() method of a class-component or executes the function body of a functional component).
If you are curious how react-redux determines when it should force this re-render (by utilizing useReducer), take another look at the source code of useSelector.
Redux uses the subscriber-pattern to get notified of updates to the state. If the root-state of redux is updated the following things happen:
useSelector hooks in your application re-run their selector function
This re-selected state is compared to the previously selected state (by default via === comparison). The second argument to useSelector can be a comparison function to change this behavior
If the re-selected state differs from the previously selected state, a re-render is triggered via the useReducer hook.
The subscriber pattern is very react-like but potentially helps save many re-renders. Calling several useSelector hooks is cheap when compared with re-renders.
First of all, I want to mention that you don't need to do a force update when you use useSelector hook. Rerender will happen automatically whenever the selected state value will be updated.
But if you need to force update the functional component you can use this approach.
import React, { useState } from 'react';
//create your forceUpdate hook
function useForceUpdate(){
const [value, setValue] = useState(0); // integer state
return () => setValue(value => ++value); // update the state to force render
}
function MyComponent() {
// call your hook here
const forceUpdate = useForceUpdate();
return (
<div>
{/*Clicking on the button will force to re-render like force update does */}
<button onClick={forceUpdate}>
Click to re-render
</button>
</div>
);
}
I highly recommend avoiding the use of this hack, in 99% of issues you can resolve them without force update. But in any case, it's good to know that there is such a possibility in the functional component exists too.
Maybe something like this could help you:
In a Class Component you could pass a property like the one below...
<Element onSomethingHappen={
()=>{
if(shouldComponentUpdate())
this.forceUpdate();
}}/>
In the function component you can call the updater like this one:
function FunctionComponent(props){
//When you need it you can update like this one...
props.onSomethingHappen();
// Here you are ;) let me know if this helps you
}
Continuing on other answers, to keep your code clean you can create a dummy state and then set it in your own forceUpdate function:
const [helper, setHelper] = useState(false);
function forceUpdate(){
setHelper(!helper);
}
Now you can just call forceUpdate() in the rest of your code:
<div onClick={() => forceUpdate()} />

Does it make sense to memoize a toggle function with useCallback?

I'm following an Advanced React Patterns with Hooks online course and there's this early example in which they create an Expandable component (say the classic accordion or collapsible panels) with the following API:
<Expandable>
<Expandable.Header>This is the header</Expandable.Header>
<Expandable.Body>This is the content</Expandable.Body>
</Expandable>
And they're using Context to pass the state expanded to Expandable's children. So far so good:
import React, { createContext, useState } from 'react'
const ExpandableContext = createContext()
const { Provider } = ExpandableContext
const Expandable = ({children}) => {
const [expanded, setExpanded] = useState(false)
const toggle = setExpanded(prevExpanded => !prevExpanded)
return <Provider>{children}</Provider>
}
export default Expandable
But then they say:
toggle acts as a callback function and it’ll eventually be invoked by Expandable.Header. Let’s prevent any future performance issues by memoizing the callback
const toggle = useCallback(
() => setExpanded(prevExpanded => !prevExpanded),
[]
)
This confuses me because according to the docs useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. But toggle doesn't have any dependencies, and still it produces a different result (setting expanded state to true or false alternatively) every time.
So, what's the point of this? What am I missing?
When the state in Expandable component will be updated, Expandable component will re-render. This will cause the toggle function to be re-created.
To prevent this, it is wrapped in useCallback hook so that toggle function is not recreated unnecessarily across re-renders.
useCallback hook is used to memoize callbacks that are passed as props to child components. This can help avoid unnecessary execution of useEffect hook or any other code that depends on referential identity of the callback function passed as a prop from the parent component.

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.

Resources