In the react hooks doc, they give an example of using the useCallback React hook as follows:
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
I have an example that has a callback that gets called with a parameter (not a or b) and it seems to work. Please explain to me what a,b are and how they are meant to be used. Below is my working code using a callback.
const signupCallback = email => {
return console.log(`sign up called with email ${email}`);
};
const memoizedsignupCallback = useCallback(() => {
signupCallback();
}, []);
and the call that uses the callback.
<SignMeUp signupCallback={memoizedsignupCallback} />
This is the array of values that the hook depends on. When these values change, it causes the hook to re-execute. If you don't pass this parameter, the hook will evaluate every time the component renders. If you pass in [], it will only evaluate on the initial render.
Documentation regarding this is available here: https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect.
If you do pass this array of parameters, it is very important to include all of the state that can change and is referenced in the hook closure. If you forget to include something, the values in the closure will become stale. There is an eslint rule that checks for this issue (the linked discussion also contains more details): https://github.com/facebook/react/issues/14920.
You are correct in that useCallback is used to memoize a function. You can think of a and b (or anything used in the second argument of useCallback) as the keys to the memoized function. When either a or b change, a new function is created.
This is useful especially when you want something to be called on an onClick that requires some values from your component's props.
Similar to your example instead of creating a new function on every render:
const Signup = ({ email, onSignup }) => {
return <button onClick={() => onSignup(email) } />;
}
you would use useCallback:
const Signup = ({ email, onSignup }) => {
const onClick = useCallback(() => onSignup(email), [email, onSignup]);
return <button onClick={onClick} />;
}
This will ensure that a new function is created and passed to onClick only if email or onSignup change.
The use of parameter a, b depends on whether the function that you are trying to execute takes them from the enclosing scope or not.
When you create a function like
const signupCallback = email => {
return console.log(`sign up called with email ${email}`);
};
const memoizedsignupCallback = useCallback(() => {
signupCallback();
}, []);
In the above case memoizedsignupCallback is created on initial render and it will have access to the values from the enclosing closure when it is created. Not if you want to access a value that lies within its closure but can update due to some interaction, you need to recreate the memoized callback and hence you would pass in the arguments to useCallback.
However in your case the value that memoizedsignupCallback uses is passed on by the caller while executing the method and hence it would work correctly
DEMO
Related
As said in docs, useCallback
Returns a memoized callback.
Pass an inline callback and an array of inputs. useCallback will return a memoized version of the callback that only changes if one of the inputs has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
But how does it work and where is the best to use it in React?
P.S. I think visualisation with codepen example will help everyone to understand it better. Explained in docs.
This is best used when you want to prevent unnecessary re-renders for better performance.
Compare these two ways of passing callbacks to child components taken from React Docs:
1. Arrow Function in Render
class Foo extends Component {
handleClick() {
console.log('Click happened');
}
render() {
return <Button onClick={() => this.handleClick()}>Click Me</Button>;
}
}
2. Bind in Constructor (ES2015)
class Foo extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('Click happened');
}
render() {
return <Button onClick={this.handleClick}>Click Me</Button>;
}
}
Assuming <Button> is implemented as a PureComponent, the first way will cause <Button> to re-render every time <Foo> re-renders because a new function is created in every render() call. In the second way, the handleClick method is only created once in <Foo>'s constructor and reused across renders.
If we translate both approaches to functional components using hooks, these are the equivalents (sort of):
1. Arrow Function in Render -> Un-memoized callback
function Foo() {
const handleClick = () => {
console.log('Click happened');
}
return <Button onClick={handleClick}>Click Me</Button>;
}
2. Bind in Constructor (ES2015) -> Memoized callbacks
function Foo() {
const memoizedHandleClick = useCallback(
() => console.log('Click happened'), [],
); // Tells React to memoize regardless of arguments.
return <Button onClick={memoizedHandleClick}>Click Me</Button>;
}
The first way creates callbacks on every call of the functional component but in the second way, React memoizes the callback function for you and the callback is not created multiple times.
Hence in the first case if Button is implemented using React.memo it will always re render (unless you have some custom comparison function) because the onClick prop is different each time, in the second case, it won't.
In most cases, it's fine to do the first way. As the React docs state:
Is it OK to use arrow functions in render methods? Generally speaking,
yes, it is OK, and it is often the easiest way to pass parameters to
callback functions.
If you do have performance issues, by all means, optimize!
useCallback and useMemo are an attempt to bypass weak spots that come with the functional programming approach chosen with React hooks. In Javascript, each entity, no matter if it is a function, variable, or whatever, is created into the memory when the execution will enter the function's code block. This is a big issue for a React that will try to detect if the component needs to be rendered. The need for rerendering is deducted based on input props and contexts. Let's see a simple example without useCallback.
const Component = () => {
const [counter, setCounter] = useState(0);
const handleClick = () => {
setCounter(counter + 1);
}
return <div>
Counter:{counter}<br/>
<button onClick={handleClick}>+1</button>
</div>
}
Note that the handleClick -function instance will be created on each function call inside the block, so the event handler's address on each call will be different. The React framework will always see the event handler as changed because of this. In the example above, React will think handleClick as a new value on each call. It simply has no tools to identify it as the same call.
What useCallback does, it internally stores the first introduced version of the function and returns it to the caller, if the listed variables have not changed.
const Component = () => {
const [counter, setCounter] = useState(0);
const handleClick = useCallback(() => {
setCounter(counter + 1);
}, [])
return <div>
Counter:{counter}<br/>
<button onClick={handleClick}>+1</button>
</div>
}
Now, with the code above, React will identify the handleClick -event handler as the same, thanks to useCallback -function call. It will always return the same instance of function and React component rendering mechanism will be happy.
Storing the function internally by the useCallback will end up with a new problem. The stored instance of the function call will not have direct access to the variables of the current function call. Instead, it will see variables introduced in the initial closure call where the stored function was created. So the call will not work for updated variables. Thats why you need need tell if some used variables have changed. So that the useCallback will store the current function call instance as a new stored instance. The list of variables as the second argument of the useCallback is listing variables for this functionality. In our example, we need to tell to useCallback -function that we need to have a fresh version of counter -variable on each call. If we will not do that, the counter value after the call will be always 1, which comes from the original value 0 plus 1.
const Component = () => {
const [counter, setCounter] = useState(0);
const handleClick = useCallback(() => {
setCounter(counter + 1);
}, [counter])
return <div>
Counter:{counter}<br/>
<button onClick={handleClick}>+1</button>
</div>
}
Now we have a working version of the code that will not rerender on every call.
It is good to notice that the useState -call is here just for the same reason. Function block does not have an internal state, so hooks are using useState, useCallback and useMemo to mimic the basic functionality of classes. In this sense, functional programming is a big step back in history closer to procedural programming.
useMemo is the same kind of mechanism as useCallback but for other objects and variables. With it, you can limit the need for component rerender, as the useMemo -function will return the same values on each function call if the listed fields have not changed.
This part of the new React hooks -approach is definitely the weakest spot of the system. useCallback is pretty much counterintuitive and really error-prone. With useCallback-calls and dependencies, it is too easy to end up chasing internal loops. This caveat we did not have with the React Class approach.
The original approach with classes was more efficient after all. The useCallback will reduce the need to rerender, but it regenerates the function again every time when some of its dependant variables will change, and matching if the variables have changes itself will make overhead. This may cause more rerenders than necessary. This is not the case with React classes.
I've made a small example to help others understand better how it behaves. You can run the demo here or read the code bellow:
import React, { useState, useCallback, useMemo } from 'react';
import { render } from 'react-dom';
const App = () => {
const [state, changeState] = useState({});
const memoizedValue = useMemo(() => Math.random(), []);
const memoizedCallback = useCallback(() => console.log(memoizedValue), []);
const unMemoizedCallback = () => console.log(memoizedValue);
const {prevMemoizedCallback, prevUnMemoizedCallback} = state;
return (
<>
<p>Memoized value: {memoizedValue}</p>
<p>New update {Math.random()}</p>
<p>is prevMemoizedCallback === to memoizedCallback: { String(prevMemoizedCallback === memoizedCallback)}</p>
<p>is prevUnMemoizedCallback === to unMemoizedCallback: { String(prevUnMemoizedCallback === unMemoizedCallback) }</p>
<p><button onClick={memoizedCallback}>memoizedCallback</button></p>
<p><button onClick={unMemoizedCallback}>unMemoizedCallback</button></p>
<p><button onClick={() => changeState({ prevMemoizedCallback: memoizedCallback, prevUnMemoizedCallback: unMemoizedCallback })}>update State</button></p>
</>
);
};
render(<App />, document.getElementById('root'));
An event handler gets recreated and assigned a different address on every render by default, resulting in a changed ‘props’ object. Below, button 2 is not repeatedly rendered as the ‘props’ object has not changed. Notice how the entire Example() function runs till completion on every render.
const MyButton = React.memo(props=>{
console.log('firing from '+props.id);
return (<button onClick={props.eh}>{props.id}</button>);
});
function Example(){
const [a,setA] = React.useState(0);
const unmemoizedCallback = () => {};
const memoizedCallback = React.useCallback(()=>{},[]); // don’t forget []!
setTimeout(()=>{setA(a=>(a+1));},3000);
return (<React.Fragment>
<MyButton id="1" eh={unmemoizedCallback}/>
<MyButton id="2" eh={memoizedCallback}/>
<MyButton id="3" eh={()=>memoizedCallback}/>
</React.Fragment>);
}
ReactDOM.render(<Example/>,document.querySelector("div"));
I've been using React for a couple years, with functional components and hooks. The most frustrating thing I've found has to do with passing callbacks. I have two concerns:
If an element receives a callback from a parent it can't really use that function in a event listener or onEvent field or in a setTimeout/setInterval, because the passed function may be redefined any time the parent rerenders.
A function that's generated at each render (like an arrow function) and passed to a child element breaks React.memo().
I've seen some limited solutions to this, such as useEventListener() but not one that addresses all the situations that concern me.
So I've written my own hook that I've called useHandler. Unlike useCallback, which generates a new function whenever the inputs change, useHandler always returns the same function. This way the function case be passed to an element using React.memo() or to a setInterval or whatever.
It call be used by the parent element (needed for the React.memo case) or by the child element (if you're worried that a passed-in function might change). Here's an example of it used in the parent:
const onClick = useHandler(event => {
//Do whatever you want. Reference useState variables or other values that might change,
})
<MyElement onClick={onClick} />
MyElement now always gets the same function, but calling that function will execute the most recent code in the parent element.
Here's an example in the child element:
const reportBack = useHandler(props.reportBack)
useEffect(() => {
setInterval(reportBack, 1000)
}, [reportBack])
The interval callback will always call the current reportBack passed from the parent.
The only thing missing for me is that I haven't modified the React eslint config to recognize that that results of useHandler cannot change and thus shouldn't generate a warning if omitted from the dependency list in useEffect or similar.
And finally, here's the source for my onHandler hook:
export function useHandler(callback) {
const callbackRef = useRef()
callbackRef.current = callback
return useRef((...args) => callbackRef.current(...args)).current
}
My questions are: Are there any flaws in this solution? Is it a good way to address my issues with React callbacks? I haven't found anything more elegant but I'm plenty willing to believe that I've missed something.
Thanks.
I can't comment on the second point as I've not used React.memo but the first point is why we have cleanup functions.
Let's say you have a function that is passed as a prop down to your component and you want to use it as an event listener.
If you are attaching this listener inside a useEffect like: window.addEventListener('someEvent', (event) => someFunctionPassedAsProp(event))
Then the useEffect can look something like:
useEffect(() => {
const handler = (event) => someFunctionPassedAsProp(event)
window.addEventListener('someEvent', handler)
return () => window.removeEventListener('someEvent', handler)
}, [someFunctionPassedAsProp])
Similarly for intervals you can do something like:
useEffect(() => {
const interval = setInterval(() => someFunctionPassedAsProp(), 1000)
return () => clearInterval(interval)
}, [someFunctionPassedAsProp])
As said in docs, useCallback
Returns a memoized callback.
Pass an inline callback and an array of inputs. useCallback will return a memoized version of the callback that only changes if one of the inputs has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
But how does it work and where is the best to use it in React?
P.S. I think visualisation with codepen example will help everyone to understand it better. Explained in docs.
This is best used when you want to prevent unnecessary re-renders for better performance.
Compare these two ways of passing callbacks to child components taken from React Docs:
1. Arrow Function in Render
class Foo extends Component {
handleClick() {
console.log('Click happened');
}
render() {
return <Button onClick={() => this.handleClick()}>Click Me</Button>;
}
}
2. Bind in Constructor (ES2015)
class Foo extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('Click happened');
}
render() {
return <Button onClick={this.handleClick}>Click Me</Button>;
}
}
Assuming <Button> is implemented as a PureComponent, the first way will cause <Button> to re-render every time <Foo> re-renders because a new function is created in every render() call. In the second way, the handleClick method is only created once in <Foo>'s constructor and reused across renders.
If we translate both approaches to functional components using hooks, these are the equivalents (sort of):
1. Arrow Function in Render -> Un-memoized callback
function Foo() {
const handleClick = () => {
console.log('Click happened');
}
return <Button onClick={handleClick}>Click Me</Button>;
}
2. Bind in Constructor (ES2015) -> Memoized callbacks
function Foo() {
const memoizedHandleClick = useCallback(
() => console.log('Click happened'), [],
); // Tells React to memoize regardless of arguments.
return <Button onClick={memoizedHandleClick}>Click Me</Button>;
}
The first way creates callbacks on every call of the functional component but in the second way, React memoizes the callback function for you and the callback is not created multiple times.
Hence in the first case if Button is implemented using React.memo it will always re render (unless you have some custom comparison function) because the onClick prop is different each time, in the second case, it won't.
In most cases, it's fine to do the first way. As the React docs state:
Is it OK to use arrow functions in render methods? Generally speaking,
yes, it is OK, and it is often the easiest way to pass parameters to
callback functions.
If you do have performance issues, by all means, optimize!
useCallback and useMemo are an attempt to bypass weak spots that come with the functional programming approach chosen with React hooks. In Javascript, each entity, no matter if it is a function, variable, or whatever, is created into the memory when the execution will enter the function's code block. This is a big issue for a React that will try to detect if the component needs to be rendered. The need for rerendering is deducted based on input props and contexts. Let's see a simple example without useCallback.
const Component = () => {
const [counter, setCounter] = useState(0);
const handleClick = () => {
setCounter(counter + 1);
}
return <div>
Counter:{counter}<br/>
<button onClick={handleClick}>+1</button>
</div>
}
Note that the handleClick -function instance will be created on each function call inside the block, so the event handler's address on each call will be different. The React framework will always see the event handler as changed because of this. In the example above, React will think handleClick as a new value on each call. It simply has no tools to identify it as the same call.
What useCallback does, it internally stores the first introduced version of the function and returns it to the caller, if the listed variables have not changed.
const Component = () => {
const [counter, setCounter] = useState(0);
const handleClick = useCallback(() => {
setCounter(counter + 1);
}, [])
return <div>
Counter:{counter}<br/>
<button onClick={handleClick}>+1</button>
</div>
}
Now, with the code above, React will identify the handleClick -event handler as the same, thanks to useCallback -function call. It will always return the same instance of function and React component rendering mechanism will be happy.
Storing the function internally by the useCallback will end up with a new problem. The stored instance of the function call will not have direct access to the variables of the current function call. Instead, it will see variables introduced in the initial closure call where the stored function was created. So the call will not work for updated variables. Thats why you need need tell if some used variables have changed. So that the useCallback will store the current function call instance as a new stored instance. The list of variables as the second argument of the useCallback is listing variables for this functionality. In our example, we need to tell to useCallback -function that we need to have a fresh version of counter -variable on each call. If we will not do that, the counter value after the call will be always 1, which comes from the original value 0 plus 1.
const Component = () => {
const [counter, setCounter] = useState(0);
const handleClick = useCallback(() => {
setCounter(counter + 1);
}, [counter])
return <div>
Counter:{counter}<br/>
<button onClick={handleClick}>+1</button>
</div>
}
Now we have a working version of the code that will not rerender on every call.
It is good to notice that the useState -call is here just for the same reason. Function block does not have an internal state, so hooks are using useState, useCallback and useMemo to mimic the basic functionality of classes. In this sense, functional programming is a big step back in history closer to procedural programming.
useMemo is the same kind of mechanism as useCallback but for other objects and variables. With it, you can limit the need for component rerender, as the useMemo -function will return the same values on each function call if the listed fields have not changed.
This part of the new React hooks -approach is definitely the weakest spot of the system. useCallback is pretty much counterintuitive and really error-prone. With useCallback-calls and dependencies, it is too easy to end up chasing internal loops. This caveat we did not have with the React Class approach.
The original approach with classes was more efficient after all. The useCallback will reduce the need to rerender, but it regenerates the function again every time when some of its dependant variables will change, and matching if the variables have changes itself will make overhead. This may cause more rerenders than necessary. This is not the case with React classes.
I've made a small example to help others understand better how it behaves. You can run the demo here or read the code bellow:
import React, { useState, useCallback, useMemo } from 'react';
import { render } from 'react-dom';
const App = () => {
const [state, changeState] = useState({});
const memoizedValue = useMemo(() => Math.random(), []);
const memoizedCallback = useCallback(() => console.log(memoizedValue), []);
const unMemoizedCallback = () => console.log(memoizedValue);
const {prevMemoizedCallback, prevUnMemoizedCallback} = state;
return (
<>
<p>Memoized value: {memoizedValue}</p>
<p>New update {Math.random()}</p>
<p>is prevMemoizedCallback === to memoizedCallback: { String(prevMemoizedCallback === memoizedCallback)}</p>
<p>is prevUnMemoizedCallback === to unMemoizedCallback: { String(prevUnMemoizedCallback === unMemoizedCallback) }</p>
<p><button onClick={memoizedCallback}>memoizedCallback</button></p>
<p><button onClick={unMemoizedCallback}>unMemoizedCallback</button></p>
<p><button onClick={() => changeState({ prevMemoizedCallback: memoizedCallback, prevUnMemoizedCallback: unMemoizedCallback })}>update State</button></p>
</>
);
};
render(<App />, document.getElementById('root'));
An event handler gets recreated and assigned a different address on every render by default, resulting in a changed ‘props’ object. Below, button 2 is not repeatedly rendered as the ‘props’ object has not changed. Notice how the entire Example() function runs till completion on every render.
const MyButton = React.memo(props=>{
console.log('firing from '+props.id);
return (<button onClick={props.eh}>{props.id}</button>);
});
function Example(){
const [a,setA] = React.useState(0);
const unmemoizedCallback = () => {};
const memoizedCallback = React.useCallback(()=>{},[]); // don’t forget []!
setTimeout(()=>{setA(a=>(a+1));},3000);
return (<React.Fragment>
<MyButton id="1" eh={unmemoizedCallback}/>
<MyButton id="2" eh={memoizedCallback}/>
<MyButton id="3" eh={()=>memoizedCallback}/>
</React.Fragment>);
}
ReactDOM.render(<Example/>,document.querySelector("div"));
I have a customHook, and I need to call it in two places. One is in the top level of the component. The other place is in a onclick function of a button, this button is a refresh button which calls the customHook to fetch new data like below. I am thinking of two approaches:
create a state for the data, call hook and set the data state in the component and in the onclick function, call hook and set the data state. However, the hook cannot be called inside another function i.e onclick in this case.
create a boolean state called trigger, everytime onclick of the button, toggle the trigger state and pass the trigger state into the myCallback in the dependent list so that myCallback function gets recreated, and the hook gets called. However, I don't really need to use this trigger state inside the callback function, and the hook gives me error of removing unnecessary dependency. I really like this idea, but is there a way to overcome this issue?
Or is there any other approaches to achieve the goal?
const MyComponent = () => {
const myCallback = React.useCallback(() => { /*some post processing of the data*/ }, []);
const data = customHook(myCallback);
return <SomeComponent data={data}>
<button onclick={/*???*/}></button>
</SomeComponent>;
};
It is possible to make your second example work with some tweaking. Instead of passing in a dependency to update the effect function, just make the effect function a stand-alone function that you pass into useEffect, but can also call in other places (e.g. you can return the effect function from your hook so your hook users can use it too)
For example:
const App = () => {
const { resource, refreshResource } = useResource()
return (
<div>
<button onClick={refreshResource}>Refresh</button>
{resource || 'Loading...'}
</div>
)
}
const useResource = () => {
const [resource, setResource] = useState(null)
const refreshResource = async () => {
setResource(null)
setResource(await fetchResource())
}
useEffect(refreshResource, [])
return { resource, refreshResource }
}
const fetchResource = async () => {
await new Promise(resolve => setTimeout(resolve, 500))
return Math.random()
}
Edit
I hadn't realized that the hook couldn't be edited. I honestly can't think of any good solutions to your problem - maybe one doesn't exist. Ideally, the API providing this custom hook would also provide some lower-level bindings that you could use to get around this issue.
If worst comes to worst and you have to proceed with some hackish solution, your solution #2 of updating the callback should work (assuming the custom hook refetches the resource whenever the parameter changes). You just have to get around the linting rule, which, I'm pretty sure you can do with an /* eslint-disable-line */ comment on the line causing the issue, if eslint is being used. Worst comes to worst, you can make a noop function () => {} that you call with your trigger parameter - that should put the linter at bay.
In Javascript and Typescript, (arrow) functions are supposed to be first class citizens. Hence, I expect that I can have function types in my React state. However, the React Hook useState does not seem to play nicely with function types.
I have the following code:
import React, { useState } from "react";
function callApi(num: number): number {
console.log(`Api called with number ${num}. This should never happen.`);
return num;
}
type Command = () => number;
function Foo() {
const [command, setCommand] = useState<Command>();
console.log(`command is ${command}.`);
// ####################
const handleButtonClick = () => {
console.log("Button clicked.");
const myCommand = () => callApi(42);
setCommand(myCommand);
};
// ####################
return (
<div>
<button onClick={handleButtonClick}>Change state</button>
</div>
);
}
export default Foo;
in this Codesandbox
When I visit the page and click the button, I get the following console output:
command is undefined.
Button clicked.
Api called with number 42. This should never happen.
command is 42.
Hence, one can see that although in my button handler the state variable command should be set to a new arrow function, React does not do that. Instead, it executes the new arrow function and stores the result in the state variable.
Why is that and how can I store functions in React state without having to use some inconvenient wrapper object?
For context: Quite often it would be convenient to build certain command functions by means of various user inputs and store them in state for later use, e.g. when building a job queue.
Why is that and how can I store functions in React state without having to use some inconvenient wrapper object?
Why?
React's useState takes in either a function (that it'll use as callback that gets the current value of the state) or a value, so if you pass () => callApi(42) it will understand it like you want the new State to be the return value of callApi when passing in a 42.
What can you do?
If you really need to do that this way (storing a function in the state), you can do something like useCommand(() => myCommand).
However, I would suggest you don't store functions in your component's state.
If you need a new instance of a function (or a new function) when something in your code has changed, use useCallback or useMemo instead.
Either will create a new function whenever one of the values specified in the dependencies array is changed.
useCallback will create a new function when their dependencies changed, so you can use it like:
function Button() {
const [buttonAction, setButtonAction] = useState(null);
// dynamicHandler will be a new function every time buttonAction changes
const dynamicHandler = useCallback(() => {
// Logic here based on the buttonAction value
}, [buttonAction]);
const handleClick = () => {
setButtonAction(BUTTON_ACTIONS.DO_SOMETHING);
};
return (
<button onClick={handleClick} />
);
}
Check out the useCallback documentation.
You can store function as object
setCommand({ function:()=>callApi(42) })
Then when you want to call the function stored in state, you can simply do command.function()