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])
Related
I wish to explain the memoization on a Redux store on my programming website.
What I have now on CodeSandbox:
import { useCallback, useMemo, memo } from "react";
import { useSelector, useDispatch } from "react-redux";
import { createSelector } from "reselect";
const Plus = memo(({ onIncrement }) => {
console.log("rendering <Plus>...");
return <button onClick={onIncrement}>+</button>;
});
export default () => {
console.log("rendering <App>...");
const v = useSelector((state) => {
console.log("useSelector() invoked");
return state;
});
const dispatch = useDispatch();
const increment = useCallback(() => dispatch({ type: "INCREMENT" }), [
dispatch
]);
return (
<div>
<span>{v}</span>
<Plus onIncrement={increment} />
</div>
);
};
As you can see, I have successfully memorized the dispatch() function with useCallback(). However, every time the button is clicked, useSelector() is called twice.
Is there a way to call it once only? I am thinking about memorization with createSelector() from the 'reselect' library. I don't understand that library. Could someone provide some guidance?
References:
Using memorizing selectors 2) The 'reselect' library
They are working correctly by having running the selectors 2 times. It would be easier and more obvious if you also print out the state (the counter) inside the selector.
Reference: https://react-redux.js.org/api/hooks#equality-comparisons-and-updates
Things will be this way:
A button and 0 are rendered on the UI.
You click the button, which dispatches an action.
The selector runs for the first time as it sees an action dispatched, it detects a change in the returned value, so it forces the component to re-render. Log is printed out with the NEW value (not the OLD one).
However, when an action is dispatched to the Redux store, useSelector() only forces a re-render if the selector result appears to be different than the last result. As of v7.1.0-alpha.5, the default comparison is a strict === reference comparison. This is different than connect(), which uses shallow equality checks on the results of mapState calls to determine if re-rendering is needed. This has several implications on how you should use useSelector()
The component re-renders, which triggers the the second log ("rendering ...").
That also leads to a re-run of the selector, which explains the third log.
When the function component renders, the provided selector function will be called and its result will be returned from the useSelector() hook. (A cached result may be returned by the hook without re-running the selector if it's the same function reference as on a previous render of the component.)
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'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.
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