React hooks: functoin, callback memo - reactjs

I was reading how to use react hooks, callback, and memo adequately. It was mentioned on one of the post that a bad implementation will cost more vs not using useMemo
We started implementing our code to exactly separate the code for UI logic and for the view only
for example, we created a custom hook that handles the UI logic
controller
const useController = () {
const [click, setClick] = useState(0)
const refresh = () => {
setClick(0)
}
const onClick = () => {
setClick(click + 1)
}
return {
click, onClick, resfresh
}
}
component
const Component = () => {
const {click, onClick, resfresh} = useController()
return(
<div>
<label onClick={onClick}>{click}</label>
<button onClick={refresh}>refresh</button>
</div>
)
}
My question here is whether the functions inside the hook are as well recreated when the component is re-rendered. If so, should it be ok to wrap a function inside a custom hook with useCallback or useMemo if there's an actual need?

Yes, you're creating new handler functions every time Component rerenders - but if the only children of the component are built-in JSX elements, it'll have no noticeable effect at all.
If you wanted to return a stable reference to the functions - which would be a good practice but not necessary - yes, it'd be perfectly fine (and expected) to use useCallback or useMemo.
const useController = () {
const [click, setClick] = useState(0);
const refresh = useCallback(() => {
setClick(0);
}, []);
const onClick = useCallback(() => {
setClick(click => click + 1);
}, []);
return {
click, onClick, refresh // make sure to spell this right
};
};
Whether useMemo or useCallback are used would matter more if you had other React components who were children of the Component component use them.
const Component = () => {
const {click, onClick, resfresh} = useController();
return(
<SomeOtherComponent {...{ click, onClick, refresh }} /> // make sure to spell this right
);
};
Making sure the references are as stable as possible can make thing easier performance-wise (...in unusual situations) and can also make their uses easier, especially in conjunction with the common exhaustive-deps linting rule.

Related

UseCallback still triggering infinitely, What should I do? [duplicate]

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"));

onCallback React Hook - best practice query

This is more of a best practice question than anything. But I'm wondering if any inline functions within a React component should typically be wrapped with an onCallback for performance? Under what circumstances would I not wrap a function like this with onCallback?
For example:
const ToolSearch = (props: ToolsSearchProps) => {
const handleOnClick = () => {
alert('do something');
}
return (
<NoCardOverflow>
<ToolSearchCollapse openState={filtersOpen} onClick={handleOnClick} />
</NoCardOverflow>
);
};
In this example should I be doing this:
const ToolSearch = (props: ToolsSearchProps) => {
const handleOnClick = useCallback(() => {
alert('do something');
},[]);
return (
<NoCardOverflow>
<ToolSearchCollapse openState={filtersOpen} onClick={handleOnClick} />
</NoCardOverflow>
);
};
I will try to explain the useCallBack with an example. We all know the definition of useCallBack, but when to use is the trick part here. So, let me take an example.
const RenderText = React.memo(({ text }) => {
console.log(`Render ${text}`);
return (<div>{text}</div>);
});
const App = () => {
const [count, updateCount] = React.useState(0);
const [lists, updateLists] = React.useState(["list 1"]);
const addListItem = () => {
updateLists([...lists, "random"]);
};
return (
<div>
{`Current Count is ${count}`}
<button onClick={() => updateCount((prev) => prev + 1)}>Update Count</button>
{lists.map((item: string, index: number) => (
<RenderText key={index} text={item} />
))}
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
jsFiddle: https://jsfiddle.net/enyctuLp/1/
In the above, there are two states.
count - Number
lists - Array
The RenderText just renders the text passed to it. (which is the item in the list). If you click on the Update Count button, the RenderText will not re-render because it is independent from the main component (App) and during the updateCount, only the App component will re-render, since it needs to update the count value.
Now, pass the addListItem into RenderText component and click on the Update Count button and see what happens.
jsFiddle: https://jsfiddle.net/enyctuLp/2/
You can see that the RenderText will re-render even though there is no change in the list array and this is BECAUSE :
when the count is updated, the App will re-render
which, will re-render the addListItem
which causes the RenderText to re-render.
To avoid this, we should use useCallBack hook.
jsFiddle: https://jsfiddle.net/enyctuLp/4/
Now, the addListItem function has been memoized and it will only change when the dependency are changed.
ToolSearchCollapse is your child component, and so wrapping your handleClick function makes sense. Coz every time the parent component re-renders a new reference of handleclick will be passed to ToolSearchCollapse, and your child will re-render every time a parent state changes, (from states that aren't passed to the child also). Using useCallback will not allow creating new references, and thus you can control when your child should render.
If you don't pass this fn to the child, I don't see any reason to use useCallback.

Question regarding benefit of React useCallback hook [duplicate]

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"));

Can't get `lodash.debounce()` to work properly? Executed multiple times... (react, lodash, hooks)

I am trying to update the state of a child component in React as the range input moves. And, I want to fire the update function to the parent component's state with Lodash's debounce function so that I don't set the state of the parent component every time range input fires an event.
However, after debounce's delay, all the events are getting fired. As if I called setTimeout functions consecutively on every range input event, but not debounce.
I can't find what am I missing here. How can I have the function passed into "debounce" get executed once after a series of range input events?
My simplified code looks like this:
import _ from 'lodash'
import React from 'react'
const Form: React.FC<Props> = props => {
const [selectedStorageSize, setSelectedStorageSize] = React.useState(props.storageSize)
const handleChangeAt = (field, payload) => {
props.handleFormChangeAt(FormField.InstanceDefs, {
...form[FormField.InstanceDefs],
[field]: payload,
})
}
const debouncedChange = _.debounce(
(field, payload) => handleChangeAt(field, payload),
500,
)
return(
<input
required
type="range"
label="Storage Size/GB"
min={50}
max={500}
value={props.selectedStorageSize}
step={5}
onChange={e => {
setSelectedStorageSize(Number(e.target.value))
debouncedChange(FormField.StorageSize, Number(e.target.value))
}}
/>
}
The Problem
_.debounce creates a function that debounces successive calls to the same function instance. But this is creating a new one every render, so successive calls aren't calling the same instance.
You need to reuse the same function across renders. There's a straightforward way to do that with hooks... and a better way to do it:
The straightforward (flawed) solution - useCallback
The most straightforward way of preserving things across render is useMemo/useCallback. (useCallback is really just a special case of useMemo for functions)
const debouncedCallback = useCallback(_.debounce(
(field, payload) => handleChangeAt(field, payload),
500,
), [handleChangeAt])
We've got an issue with handleChangeAt: it depends on props and creates a different instance every render, in order to capture the latest version of props. If we only created debouncedCallback once, we'd capture the first instance of handleChangeAt, and capture the initial value of props, giving us stale data later.
We fix that by adding [handleChangeAt], to recreate the debouncedCallback whenever handleChangeAt changes. However, as written, handleChangeAt changes every render. So this change alone won't change the initial behavior: we'd still recreate debouncedCallback every render. So you'd need to memoize handleChangeAt, too:
const { handleFormChangeAt } = props;
const handleChangeAt = useCallback((field, payload) => {
handleFormChangeAt(/*...*/)
}, [handleFormChangeAt]);
(If this sort of memoizing isn't familiar to you, I highly recommend Dan Abramov's Complete Guide to useEffect, even though we aren't actually using useEffect here)
This pushes the problem up the tree, and you'll need to make sure that whatever component provides props.handleFormChangeAt is also memoizing it. But, otherwise this solution largely works...
The better solution - useRef
Two issues with the previous solution: as mentioned it pushes the problem of memoization up the tree (specifically because you're depending on a function passed as a prop), but the whole point of this is so that we can recreate the function whenever we need to, to avoid stale data.
But the recreating to avoid stale data is going to cause the function to be recreated, which is going to cause the debounce to reset: so the result of the previous solution is something that usually debounces, but might not, if props or state have changed.
A better solution requires us to really only create the memoized function once, but to do so in a way that avoids stale data. We can do that by using a ref:
const debouncedFunctionRef = useRef()
debouncedFunctionRef.current = (field, payload) => handleChangeAt(field, payload);
const debouncedChange = useCallback(_.debounce(
(...args) => debouncedFunctionRef.current(...args),
500,
), []);
This stores the current instance of the function to be debounced in a ref, and updates it every render (preventing stale data). Instead of debouncing that function directly, though, we debounce a wrapper function that reads the current version from the ref and calls it.
Since the only thing the callback depends on is a ref (which is a constant, mutable object), it's okay for useCallback to take [] as its dependencies, and so we'll only debounce the function once per component, as expected.
As a custom hook
This approach could be moved into its own custom hook:
const useDebouncedCallback = (callback, delay) => {
const callbackRef = useRef()
callbackRef.current = callback;
return useCallback(_.debounce(
(...args) => callbackRef.current(...args),
delay,
), []);
};
const { useCallback, useState, useRef, useEffect } = React;
const useDebouncedCallback = (callback, delay, opts) => {
const callbackRef = useRef()
callbackRef.current = callback;
return useCallback(_.debounce(
(...args) => callbackRef.current(...args),
delay,
opts
), []);
};
function Reporter({count}) {
const [msg, setMsg] = useState("Click to record the count")
const onClick = useDebouncedCallback(() => {
setMsg(`The count was ${count} when you clicked`);
}, 2000, {leading: true, trailing: false})
return <div>
<div><button onClick={onClick}>Click</button></div>
{msg}
</div>
}
function Parent() {
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => setCount(x => x+1), 500)
}, [])
return (
<div>
<div>The count is {count}</div>
<Reporter count={count} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<div id="root" />
I used useCallback with _.debounce, but faced with a eslint error, 'React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead.'
Finally, I found this issue, and used useMemo.
before:
const debouncedMethod = useCallback(
debounce((arg) => {
someMethod(arg);
}, 500),
[someMethod],
);
after:
const debouncedMethod = useMemo(
() =>
debounce((arg) => {
someMethod(arg);
}, 500),
[someMethod],
);
lodash.debounce creates a new function invocations of which will be debounced. Use case scenario is creating it once, storing result in a variable and calling it multiple times so the calls get debounced. You are creating a new debounced function every time you render a component so every new render you get a new debounce scope
You want to call const debouncedChange = _.debounce(...) only once per component instance. I am not very much familiar with hooks, but guess you can do this
const [debouncedChange] = React.useState(_.debounce(
(field, payload) => handleChangeAt(field, payload),
500,
))
this will create your function once during render and reuse what's created across successive renders

Using useMemo instead of React.memo syntax issue

I need to make a demonstration of using React Hooks useMemo. I have working code that is as follows that does what I want:
const SpeakerCardDetail = React.memo(
({id,...
I found a link that shows that I could use syntax more like this but I can't figure it out exactly.
This is as far as I got:
const SpeakerDetail = React.useMemo(() => {
({ id,
Clearly not it though. I do get that React.memo solves the problem but I do need to show useMemo in action and am hoping there is an alternative syntax that I can use.
React.memo and React.useMemo are not equivalent at all (don't rely on naming similarity). Here's a quote from React.memo doc:
React.memo is a higher order component.
So it's a HOC that can optimize rendition of your component given that it renders the same output with the same properties.
React.useMemo on the other hand is more generic and returns a memoized value:
Pass a “create” function and an array of dependencies. useMemo will
only recompute the memoized value when one of the dependencies (either a or b) has
changed.
const memoizedValue = useMemo(
() => computeExpensiveValue(a, b),
[a, b]
);
And while it can be hacked to be used instead of React.memo, it's not its purpose and it will add to the confusion more than it will help. useMemo is a hook and is subject to the certain usage rules.
And there's this warning as well:
In the future, React may choose to “forget” some previously memoized
values and recalculate them on next render, e.g. to free memory for
offscreen components. Write your code so that it still works without
useMemo — and then add it to optimize performance.
While memo is a HOC and useMemo is a hook, you can use them the achieve the same result.
For context, HOC is an older React pattern that has been used for many years with class-based and functional components alike. You can still use it today (there's no plan for deprecation).
Hooks is a relatively new concept (about a year) that enhances functional components and in many cases drastically simplifies code. That's why many developers are moving towards using hooks.
Anyway, both memo and useMemo take two arguments: a function and props. If none of the props change on subsequent re-renders, the function is not executed again and instead returns the previous result. This, in effect, replaces shouldComponentUpdate callbacks, with a purely functional approach.
With memo, your code would look like this:
const SpeakerCardDetail = React.memo(
(props) => <div>{props.name}</div>
)
With useMemo, you'd write:
const SpeakerCardDetail = (props) => useMemo(() => <div>{props.name}</div>)
Notice that useMemo is used inside of your component function, while memo wraps the function.
More traditionally, useMemo could be written as:
function SpeakerCardDetail(props) {
return useMemo(
() => <div>{props.name}</div>
)
}
Now, the code above would re-render every time, making the useMemo function a bit useless. To make it work its magic, we need to add the second argument. (memo still works even without specifying the second argument but you can add it to customize it)
There's a slight difference in the format of the second argument. memo expects a function that compares previous and current props, just like shouldComponentUpdate does for class components.
const SpeakerCardDetail = React.memo(
(props) => <div>{props.name}</div>
,
// return true if passing nextProps to render would return the same result as passing prevProps to render, otherwise return false
(prevProps, nextProps) => prevProps.name === nextProps.name
)
useMemo, on the other hand, expects an array as the second argument. Whenever the values in the array change, the function would be executed again.
function SpeakerCardDetail(props) {
return useMemo(
() => <div>{props.name}</div>
,
[props.name]
)
}
There's really no more magic than that. Both memo and useMemo are used to memoize the result of a function, the only difference is memo is a HOC (and can be used to wrap both class and functional components) which useMemo is a hook (and can only be used inside functional components).
To summarise React.memo vs useMemo / TLDR:
React.memo is a higher-order component (HOC for short) that will memoize a react component based on the props.
export function SomeComponent({ num }) {
return <p>{num * 10}</p>
}
export default React.memo(SomeComponent, function areEqual(
prevProps,
nextProps
) {
if (prevProps.num !== nextProps.num) {
return false
}
return true
})
useMemo is a react hook that will memoize the value that is returned from the function you provide it.
export function SomeComponent({ num }) {
const res = useMemo(() => num * 10, [num])
return <p>{res}</p>
}
Source
React.Memo
Using React.memo will cause React to skip rendering a component if its props have not changed.
Example:
const Child = React.memo(props => {
console.log("rendered");
return <React.Fragment>{props.name}</React.Fragment>;
});
class App extends React.Component {
state = {
value: 1,
name: "Jioke"
};
handleClick = () => {
this.setState({
value: this.state.value + 1
});
};
render() {
return (
<React.Fragment>
<Child name={this.state.name} />
<div>{this.state.value}</div>
<button onClick={this.handleClick}>+</button>
</React.Fragment>
);
}
}
When we click button, Child Component does not re-render(rendered is displayed only 1)
Notes: The more props, the more calculations, comparison(check is render or not) added comparison cost is not worth it for a "simple" component in terms of render, reconcile, DOM change and side-effect costs. So be careful to decide use it or not
UseMemo
useMemo will cache a value so that it does not need to be recalculated each times components is re-render. It saves return value of function and returns if the inputs are not changed.
Example:
import { useState, useMemo } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const calculation = useMemo(() => expensiveCalculation(count), [count]);
const increment = () => {
setCount((c) => c + 1);
};
const addTodo = () => {
setTodos((t) => [...t, "New Todo"]);
};
return (
<div>
<div>
<h2>My Todos</h2>
{todos.map((todo, index) => {
return <p key={index}>{todo}</p>;
})}
<button onClick={addTodo}>Add Todo</button>
</div>
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
<h2>Expensive Calculation</h2>
{calculation}
</div>
</div>
);
};
const expensiveCalculation = (num) => {
console.log("Calculating...");
for (let i = 0; i < 1000000000; i++) {
num += 1;
}
return num;
};
ReactDOM.render(<App />, document.getElementById('root'));
we have an expensive function that runs on every render.
When changing the count or adding a todo, you will notice a delay in execution.
So when we use useMemo the expensive function will only run when its dependencies have changed.
In the following example, the expensive function will only run when count is changed and not when todo's are added.
Please use the below simple example to better understand the answers above. The example demonstrates these scenarios in the simplest way:
memo
If the props do not change, the component won't be re-rendered.
useMemo
If the dependencies do not change, the component won't be re-rendered.
If the dependencies do not change, the function (useful for expensive functions) won't be re-run.
// index.js
import React , { useState, useMemo } from 'react';
import ReactDOM from 'react-dom/client';
const Child1 = (props) => {
console.log("Child1");
return <p>Child 1</p>;
};
const Child2 = React.memo((props) => {
console.log("Child2");
return <p>Child 2</p>;
});
const Child3 = (props) => {
console.log("Child3");
return <p>Child 3</p>;
};
const expensiveCalculation = (label) => {
console.log(label);
return label;
};
function App() {
console.log("App");
const [count, setCount] = useState(0);
const child3 = useMemo(() => <Child3 />, []);
const calculation1 = expensiveCalculation("Expensive calculation without useMemo");
const calculation2 = useMemo(() => expensiveCalculation("Expensive calculation with useMemo"), []);
return (
<>
<button onClick={() => {setCount(c => c + 1);}}>Increase count</button>
<p>Current count: {count}</p>
<Child1 />
<Child2 />
{child3}
<p>{calculation1}</p>
<p>{calculation2}</p>
</>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Input/output:
// Initial render
App
Expensive calculation without useMemo
Expensive calculation with useMemo
Child1
Child2
Child3
// Click on the "Increase count" button and observe the impact of memo and useMemo.
App
Expensive calculation without useMemo
Child1
Notes:
I have taken a very simple example to make it easy to explain. Please research a little bit more on the arguments memo and useMemo takes.

Resources