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.
Related
I'm experiencing something similar to this:
Should Custom React Hooks Cause Re-Renders of Dependent Components?
So, I have something like this:
const ComponentA = props => {
const returnedValue = useMyHook();
}
And I know for a fact that returnedValue is not changing (prev. returnedValue is === to the re-rendered returnedValue), but the logic inside of the useMyHook does cause internal re-renders and as a result, I get a re-render in the ComponentA as well.
I do realize that this is intentional behavior, but what are my best options here? I have full control over useMyHook and returnedValue. I tried everything as I see it, caching(with useMemo and useCallback) inside of useMyHook on returned value etc.
Update
Just to be more clear about what I'm trying to achieve:
I want to use internal useState / useEffect etc inside of useMyHook and not cause re-render in the ComponentA
Try to find out the reason of re-rendering in your hook, probably caused due to a state update. If it is due to updation of states in useEffect then give the states and values as dependency. If your states are updating in a function, do try to call the function in order to invoke.
If these doesn't work, try to use ref. useRef hook can be used to prevent such re-renders as it will provide you with .current values.
It would be better if you remove your useEffect dependencies one by one so that you can know what dependency is causing the error and create a ref for the same.
You can share a codesandbox and I would be happy to help you out with it.
I want to use internal useState / useEffect etc inside of useMyHook and not cause re-render in the ComponentA
If you want to avoid the renders triggered by useState() or useEffect() then they are not the right tools.
If you need to hold some mutable state without triggering renders then consider using useRef() instead.
This question has a nice comparison of the differences between useState() and useRef().
Here is a simple example:
const ComponentState = () => {
const [value, setValue] = useState(0);
return (<>
<button onClick={() => {
// Will trigger render
setValue(Math.random());
}></button>
</>);
}
const ComponentRef = () => {
const valueRef = useRef(0);
return (<>
<button onClick={() => {
// Will not trigger render
valueRef.current = Math.random();
}></button>
</>);
}
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.
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])
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.
Sometimes a component needs to process passed props and save in its state. Since the processing can be heavy it's good to be done just once. Before hooks, it's typically done in constructor or componentDidMount.
Now coming into hooks, it can be achieved with useEffect, passing [] as the second parameter to run only once but I feel it's not the best place - what we're doing is processing props and save in state, which is not a Side Effect. From the docs: "Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects." Don't think pre-process belongs any of these.
So where's the best place to do it with hooks?
import React, {useMemo} from 'react';
const someExpensiveFunction = (a, b) => {
// expensive stuff happens here
}
const someFunctionalComponent = ({ prop1, prop2 }) => {
const someVariableDependentOnProps = useMemo(() => someExpensiveFunction(prop1, prop2), [prop1, prop2]);
return <div>{someVariableDependentOnProps}</div>
}
According to the docs:
useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.
https://reactjs.org/docs/hooks-reference.html#usememo