Should I include setState in useCallback's array of dependencies? - reactjs

const [active, setActive] = useState(false);
const onActiveChanged = useCallback(
isActive => () => {
// do something
setActive(isActive);
},
[setActive], // or just [] is okay?
);
When using useState and useCallback (or useMemo) together, should I include setState in the array of dependencies?

The recommendation for that is also on React Docs - Hooks API Reference.
The setState function is used to update the state. It accepts a new
state value and enqueues a re-render of the component.
setState(newState);
During subsequent re-renders, the first value
returned by useState will always be the most recent state after
applying updates.
Note
React guarantees that setState function identity is stable and won’t
change on re-renders. This is why it’s safe to omit from the useEffect
or useCallback dependency list.

The purpose of useCallback as you rightly hinted is to memoise:
useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).
And as for what useMemo is intended for:
You may rely on useMemo as a performance optimization, not as a semantic guarantee.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
But as a rule, useState is stable between renders (i.e. pre-memoised), so you should not need to memoise it again.
The question then comes, is your 'do something' below an expensive calculation? It shouldn't be to onerous to make use of useCallback, but it could well be boilerplate code you don't need, and could make almost direct use of your setActive function.
const [active, setActive] = useState(false);
const onActiveChanged = useCallback(
isActive => () => {
// do something
setActive(isActive);
},
[setActive], // or just [] is okay?
);
Another way to prevent unnecessary dependencies, in your useCallback and other hooks, is to make use of functional updates. The result being that you can have these:
const [active, setActive] = useState(false);
const [expensiveCalc, setExpensiveCalc] = useState(false);
const onExpensiveCalc = useCallback(
expensiveInput => () => {
const newState = doExpensiveCalc(expensiveInput);
expensiveCalc(newState);
},
[setActive], // here for completeness
);
return (<>
// expensive calculation
<button onClick={onExpensiveCalc}>Do lengthy calculation</button>
// cheap calculation, using functional updates
<button onClick={() => setActive(prevBoolean => !prevBoolean)}>Cheap Set Active</button>
</>)
Do note, that there's a little nuance to how set state works in an onClick, and you should make use of an arrow function, so your setActive is run on click, not render. This is shown in the second answer above, but without explanation.
See also: What is useState() in React?

Related

React Hooks: Is more performant to use `useRef` over `useState` to keep track of the boolean toggle state?

Overview
I'm implementing a useToggle react hook and I want to know if it is more performant to use useRef over useState to keep track of the boolean toggle state. Since useRef doesn't cause re-renders, this should be more efficient right?
useToggle (Using useRef)
const useToggle = () => {
const stateRef = useRef<boolean>(false);
const toggle = useCallback(() => stateRef.current = !stateRef.current, []);
return [stateRef, toggle];
}
useToggle (Using useState)
const useToggle = () => {
const [state, setState] = useState<boolean>(false);
const toggle: React.Dispatch<React.SetStateAction<boolean>> = useCallback(() => setState(state => !state), []);
return [state, toggle];
}
I think, its fine to use useRef until you don't want any re-renders i.e. any UI changes.
But I do agree to what #Mario said.
Though, you can prevent re-renders using useRef but it doesn't mean you should use useRef instead of useState.
You should try to minimize the use of states.
Here you haven't mentioned what useToggle is for; thats y its hard to say whether you should use it or not.
And we generally don't compare them as none of then can replace each other; both of them are made for different use cases.
This blog will give you an idea of when to use what.
I am not sure about the performance but, to be honest, my approach would be the useState one. The reason behind it is simply the one you gave: It causes updates.
What is the point on creating a useToggle hook that returns the state of the toggle if you do not want to use it to update something?

Difference between useEffect and useMemo

I'm trying to wrap my head around what exactly the useMemo() React hook is.
Suppose I have this useMemo:
const Foo = () => {
const message = useMemo(() => {
return readMessageFromDisk()
}, [])
return <p>{message}</p>
}
Isn't that exactly the same as using a useState() and useEffect() hook?
const MyComponent = () => {
const [message, setMessage] = useState('')
useEffect(() => {
setMessage(readMessageFromDisk())
}, [])
return <p>{message}</p>
}
In both cases the useMemo and useEffect will only call if a dependency changes.
And if both snippes are the same: what is the benefit of useMemo?
Is it purely a shorthand notation for the above useEffect snippet. Or is there some other benefit of using useMemo?
useEffect is used to run the block of code if the dependencies change. In general you will use this to run specific code on the component mounting and/or every time you're monitoring a specific prop or state change.
useMemo is used to calculate and return a value if the dependencies change. You will want to use this to memoize a complex calculation, e.g. filtering an array. This way you can choose to only calculate the filtered array every time the array changes (by putting it in the dependency array) instead of every render.
useMemo does the same thing as your useEffect example above except it has some additional performance benefits in the way it runs under the hood.

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

More advanced comparison in React's useEffect

I am looking for a way to perform more advanced comparison instead of the second parameter of the useEffect React hook.
Specifically, I am looking for something more like this:
useEffect(
() => doSomething(),
[myInstance],
(prev, curr) => { /* compare prev[0].value with curr[0].value */ }
);
Is there anything I missed from the React docs about this or is there any way of implementing such a hook on top of what already exists, please?
If there is a way to implement this, this is how it would work: the second parameter is an array of dependencies, just like the useEffect hook coming from React, and the third is a callback with two parameters: the array of dependencies at the previous render, and the array of dependencies at the current render.
you could use React.memo function:
const areEqual = (prevProps, nextProps) => {
return (prevProps.title === nextProps.title)
};
export default React.memo(Component, areEqual);
or use custom hooks for that:
How to compare oldValues and newValues on React Hooks useEffect?
In class based components was easy to perform a deep comparison. componentDidUpdate provides a snapshot of previous props and previous state
componentDidUpdate(prevProps, prevState, snapshot){
if(prevProps.foo !== props.foo){ /* ... */ }
}
The problem is useEffect it's not exactly like componentDidUpdate. Consider the following
useEffect(() =>{
/* action() */
},[props])
The only thing you can assert about the current props when action() gets called is that it changed (shallow comparison asserts to false). You cannot have a snapshot of prevProps cause hooks are like regular functions, there aren't part of a lifecycle (and don't have an instance) which ensures synchronicity (and inject arguments). Actually the only thing ensuring hooks value integrity is the order of execution.
Alternatives to usePrevious
Before updating check if the values are equal
const Component = props =>{
const [foo, setFoo] = useState('bar')
const updateFoo = val => foo === val ? null : setFoo(val)
}
This can be useful in some situations when you need to ensure an effect to run only once(not useful in your use case).
useMemo:
If you want to perform comparison to prevent unecessary render calls (shoudComponentUpdate), then useMemo is the way to go
export React.useMemo(Component, (prev, next) => true)
But when you need to get access to the previous value inside an already running effect there is no alternatives left. Cause if you already are inside useEffect it means that the dependency it's already updated (current render).
Why usePrevious works
useRef isn't just for refs, it's a very straightforward way to mutate values without triggering a re render. So the cycle is the following
Component gets mounted
usePrevious stores the inital value inside current
props changes triggering a re render inside Component
useEffect is called
usePrevious is called
Notice that the usePrevious is always called after the useEffect (remember, order matters!). So everytime you are inside an useEffect the current value of useRef will always be one render behind.
const usePrevious = value =>{
const ref = useRef()
useEffect(() => ref.current = value,[value])
}
const Component = props =>{
const { A } = props
useEffect(() =>{
console.log('runs first')
},[A])
//updates after the effect to store the current value (which will be the previous on next render
const previous = usePrevious(props)
}
I hit the same problem recently and a solution that worked for me is to create a custom useStateWithCustomComparator hook.
In your the case of your example that would mean to replace
const [myInstance, setMyInstance] = useState(..)
with
const [myInstance, setMyInstance] = useStateWithCustomComparator(..)
The code for my custom hook in Typescript looks like that:
const useStateWithCustomComparator = <T>(initialState: T, customEqualsComparator: (obj1: T, obj2: T) => boolean) => {
const [state, setState] = useState(initialState);
const changeStateIfNotEqual = (newState: any) => {
if (!customEqualsComparator(state, newState)) {
setState(newState);
}
};
return [state, changeStateIfNotEqual] as const;
};

Update React Hooks State During Render Using useMemo

Can useMemo be used just to avoid extra referential equality checking code/vars when setting state during a render?
Example: useMemo with a setState during render taken from this rare documented use case:
function ScrollView({row}) {
let [isScrolling, setIsScrolling] = useState(false);
const lessCodeThanCheckingPrevRow = useMemo(
() => {
// Row changed since last render. Update isScrolling.
setIsScrolling(true); // let's assume the simplest case where prevState isn't needed here
},
[row]
);
return `Scrolling down: ${isScrolling}`;
}
Above drastically reduces code and extra vars, only otherwise used for equality checks, so why do the docs imply referential equality checks should be done manually?
This seems to be an elegant way to reduce boiler plate to me. I created a codesandbox to validate its behaviour.
const UnitUnderTest = ({prop}) => {
let [someState, setSomeState] = useState(false);
const lessCodeThanCheckingPrevRow = useMemo(
() => setSomeState(current => !current),
[prop],
);
useEffect(() => console.log('update finished'), [prop])
console.log('run component');
return `State: ${someState}`;
}
const App = () => {
const [prop, setProp] = useState(false);
const handleClick = () => setProp(current => !current);
return (
<div>
<button onClick={handleClick} >change prop</button>
<UnitUnderTest prop={prop} />
</div>
)
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Output when clicking the button to change the prop passed to the component:
> run component
> run component
> update finished
As you can see the component has been run twice before the update cycle completed. This is equivalent to the the behaviour of getDerivedStateFromProps.
I guess that there is no deeper thought behind why the docs propose a slightly different technique. In a way this is a manual check too but in a neat way. +1 for the idea.
Use the useEffect hook for this behavior. useMemo is used to store a value that might not necessarily change over each renders, so that you avoid useless re-calculation of that value
if the problem is having a value that changes on every user action which in this case is scrolling, then useMemo is not the best way to go. Perhaps useCallback is a better option. The code block would be the same as you have defined it above, but instead of useMemo, useCallback, and include the state in the dependency array.

Resources