React: useEffect vs useMemo vs useState - javascript

I was trying to find a concise answer to this on the web without luck.
Is the following correct regarding the differences between useEffect, useMemo and useState?
Both useState and useMemo will remember a value across renders. The difference is that:
useMemo does not cause a re-render, while useState does
useMemo only runs when its dependencies (if any) have changed, while setSomeState (second array item returned by useState) does not have such a dependency array
Both useMemo and useEffect only runs when their dependencies change (if any). The difference is that:
useEffect runs after a render happens, while useMemo runs before
Any other key differences I have missed?

Your points are basically correct, some minor clarification:
useState is causing a re-render on the call of the setState method (second element in the array returned). It does not have any dependencies like useMemo or useEffect.
useMemo only recalculates a value if the elements in its dependency array change (if there are no dependencies - i.e. the array is empty, it will recalculate only once). If the array is left out, it will recalculate on every render. Calling the function does not cause a re-render. Also it runs during the render of the component and not before.
useEffect is called after each render, if elements in its dependency array have changed or the array is left out. If the array is empty, it will only be run once on the initial mount (and unmount if you return a cleanup function).
You can always check Hooks API Reference, which is a pretty solid documentation in my opinion

The return value of useEffect(callback, [dependency]) is void and It executes after render().
The return value of useMemo(callback, [dependency]) is NOT void but memoized value and It executes DURING render().
useEffect() can give same optimization as of useMemo() under the following circumstances:
The state variable used in the expensive computation (i.e., count1) is the only dependency of the useEffect.
When we don't mind storing the expensively computed value in state variable.
const [count1, setCount1] = useState(0);
const [expensiveValue, setExpensiveValue] = useState(null);
useEffect(() => {
console.log("I am performing expensive computation");
setExpensiveValue(((count1 * 1000) % 12.4) * 51000 - 4000);
}, [count1]);
Only difference is, useEffect() makes the expensively computed value available after render() while useMemo() makes the value available during the render().
Most of the time it does not matter because if that value has been calculated for rendering in the UI, useEffect() and useMemo() both will make the value available before browser finishes painting.

useMemo Used to memoize calculations/values that belong to the component but not necessarily to the component state e.g. validations, methods that rely on component and must return a value;
const validEmail = React.useMemo(() => validateEmail(email), [email])
/* Now use 'validEmail' variable across the component,
whereas 'useEffect' return value is void and only used for unmounting duties,
like unsubscribing from subscription e.g. removeInterval*/
from docs:
Remember that the function passed to useMemo runs during rendering. Don’t do anything there that you wouldn’t normally do while rendering. For example, side effects belong in useEffect, not useMemo.
useEffect Side effects:
Mutations, subscriptions, timers, logging, and
other side effects
setState's setTimeout's setInterval' ref assignments, API calls, or anything that doesn't perform heavy calculations more belong in here.
Also remember that optimisation comes at a cost, therefore React suggests to use useMemo only when memoization/optimisation is necessary and in other cases rely on useEffect when necessary:
You may rely on useMemo as a performance optimization, not as a semantic guarantee.
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.
[state, setState] = useState() that is to update the given state variable which stays stable during re-renders and calling setState attached to it triggers a re-render. Purpose of this hook is not much relatable to the above two hooks.

Related

Do props that are functions belong in a useEffect dependencyArray?

Say I have the following code:
const ReactFunction = ({...props}) => {
useEffect(() => { props.function(props.value) }, [props.value])
return <input value={props.value} onChange={props.onChange} />
}
const ReactFunctionWrapper = () => {
const [value, setValue] = setState(0)
const logger = (e) => {console.log(e}
return <ReactFunction onChange={setValue} value={value} function={function} />
}
Should the function() prop be in the dependencyArray of the useEffect as well even though it's a function and should never change?
What exactly should be going into the dependency array?
Technically, yes. Functions can appear in useEffect's dependency array. Function pointers change on each refresh unless you use some cache feature to cache the function like useMemo or useCallback.
Should the function() prop be in the dependencyArray of the useEffect
as well even though it's a function and should never change?
If the function doesn't references any value value from props or state or any other value derived from props or state, only then it is safe to omit that function from the dependency array of the useEffect hook or any other hook that has a dependency array, for example, useCallback.
If the function uses props or state, then it is not safe to omit it from the dependency array. You could wrap the function in useCallback hook to avoid creating new function reference every time the parent component re-renders.
For details on what could happen if you omit a function from useEffect's hook dependency array, see Is it safe to omit functions from the list of dependencies?
There are some functions that are guaranteed to not change and are safe to omit from the dependency array. For example: dispatch function returned by useReducer hook or state update function returned by useState hook. They are safe to omit but still won't hurt if you add them to the dependency array if they are used inside useEffect hook.
What exactly should be going into the dependency array?
Everything in component's scope that participates in react's data flow and is used inside the useEffect hook's callback function. Same is true for useMemo and useCallback hooks.
The 2nd Parameter in React.useEffect() distinct when the effect is invoked. It's when the value of your 2nd Parameter changes, React runs the effect
Go checkout https://youtu.be/dpw9EHDh2bM?t=3629

useCallback: When to use it? Is there any drawback / performance issue?

There are articles related to what is useCallback and to use it when necessary. But in a big project it's very difficult to understand when to start using this.
So, if I use it for every use case, will it hit the performance of the application.
useCallback and useMemo are helper hooks with the main purpose off adding an extra layer of dependency check to ensure synchronicity. Usually you want to work with useCallback to ensure a stable signature to a prop which you know how will change and React doesn't.
A function(reference type) passed via props for example
const Component = ({ setParentState }) =>{
useEffect(() => setParentState('mounted'), [])
}
Lets assume you have a child component which uppon mounting must set some state in the parent (not usual), the above code will generate a warning of undeclared dependency in useEffect, so let's declare setParentState as a dependency to be checked by React
const Component = ({ setParentState }) =>{
useEffect(() => setParentState('mounted'), [setParentState])
}
Now this effect runs on each render, not only on mounting, but on each update. This happens because setParentState is a function which is recreated every time the function Component gets called. You know that setParentState won't change it's signature overtime so it's safe to tell React that. By wrapping the original helper inside an useCallback you're doing exactly that (by adding another dependency check layer).
const Component = ({ setParentState }) =>{
const stableSetter = useCallback(() => setParentState(), [])
useEffect(() => setParentState('mounted'), [stableSetter])
}
There you go. Now React knows that stableSetter won't change it's signature inside the lifecycle therefore the effect do not need run unecessarily.
On a side note useCallback it's also used like useMemo, to optmize expensive function calls (memoization).
The two main purposes of useCallback are
Optimize child components that rely on reference equality to prevent unnecessary
renders. Font
Memoize expensive computing calculations
Is there any drawback/performance?
useCallback is mainly used to optmize performance by changing stuff only when it's needed. It does that by adding a layer of dependencies just like useEffect (and similar to how React itself knows how something must change in the DOM), but as every single performance optimization technique, it can shoot backwards, if you fill your entire application with unecessary useCallback, useMemo, React.memo the performance will go the other way around.
So the key is to use wisely, carefully choosing what needs to have a stable signature and what doesn't.

Getting ref null inside useCallback hook

I'm trying to set the value of input using ref but I'm getting ref null inside useCallback Hook.
let inputRef = useRef();
const search = useCallback(
(data) => {
console.log(inputRef);
},
[inputRef],
);
return <input type="text" ref={inputRef} />
and It's showing null in the browser console when I call this function.
this is just an example showing, what I'm trying to achieve.
The problem isn't that he's not passing in a null value, a ref is initialized to null by default.
The problem is that useCallback() memoizes and caches the result of this callback function before the first render, before the component to which this ref is assigned has been mounted. It then waits for inputRef to change but inputRef is just a reference to an object; even if the component that inputRef.current points to changes, the value of inputRef does not as it is only being compared by reference equality.
So, since the function has no reason to update as the value of inputRef is not going to change -- it evaluates to the cached result of the search callback which was computed and cached before the DOM mounted and it outputs null to console.
What you're looking for is a Callback Ref
The other thing that I'd like to point out is that the useCallback hook is not necessary for this use-case and will provide no benefit. The only correct usage of useCallback() is to cache the result of expensive function calls so that they don't recompute every time render is called (this is known as memoization).
The arbitrary usage of the useCallback() hook is an anti-pattern-- it is not required for the overwhelming majority of callbacks in React, and can reduce performance when used incorrectly by creating additional overhead.

Code Execution Directly in a React Function Component, When Multiple States Causes Multiple Re-Renders

I have component that uses both useState() and other custom hooks multiple times.
I want to act based on these state values. I could do that directly in the function component's body:
const MyComponent = () => {
const [someState, setSomeState] = useState(false);
const [otherState, setOtherState] = useState(false);
const customHookValue = useCustomHook();
if (someState) foo();
const foo = () => setOtherState(!otherState);
if (customHookValue > 10) bar();
const bar = () => setSomeState(somestate > customHookValue);
}
However, every time someState changes (and a re-render happens) the second conditional also runs, causing bar() to run if the conditional passes. This feels unnatural. Logically, bar() should only run when customHookValue changes, as someState only changes if customHookValue has changed since last render.
In summary, a re-render caused by a change to someState should not cause a bunch of unrelated state setting functions to run again. Even though re-running them causes no change in the outcome of the program, it is not logically right. They only need to re-run when their corresponding conditional changes. It could effect performance.
This must be a common challenge in React. I am quite new to it, so I do not know what the approach to solve this would be.
Questions
How would I solve the above in the recommended manner?
Would I have to wrap every conditional in a useEffect or a useMemo?
EDIT:
Updated the second conditional, to make my question clearer (it should depend on customHook).
CLARIFICATION:
As it might not have been clear, my issue is as follows. When state changes, a re-render occurs. This causes all functions in the component's body to re-run. Now, if we have many useState in a component, and only one changes, a bunch of potentially unrelated state-changing and potentially expensive functions I have defined in the components body will run. These state-changing functions would only have to run if the state values they are trying to set has changed. If the values they are setting has not changed, it is unnecessary for them to run again. But, the re-render reruns all functions in the component's body regardless.
It looks like (as others have suggested) you want useEffect:
useEffect(() => {
if (someState) {
setOtherState(!otherState)
}
}, [someState, otherState])
useEffect(() => {
if (customHookValue > 10) {
setSomeState(someState > customHookValue)
}
}, [customHookValue])
Since you only want the setSomeState to run if customHookValue changes, make it the only item in the dependencies array passed to useEffect.
The exhaustive-deps eslint-plugin-react-hooks will complain about the second useEffect, since the function depends on the value of someState, even though someState will only potentially change if customHookValue changes. I also wouldn't worry about things potentially affecting performance until they do. I don't know a ton about the internals of React, but it does some things under the hood to avoid re-renders it doesn't need to do, and can do multiple renders before an actual update is painted.

Gatsby: Context update causes infinite render loop

I am trying to update context once a Gatsby page loads.
The way I did it, the context is provided to all pages, and once the page loads the context is updated (done with useEffect to ensure it only happens when the component mounts).
Unfortunately, this causes an infinite render loop (perhaps not in Firefox, but at least in Chrome).
Why does this happen? I mean, the context update means all the components below the provider are re-rendered, but the useEffect should only run once, and thats when the component mounts.
Here is the code: https://codesandbox.io/s/6l3337447n
The infinite loop happens when you go to page two (link at bottom of page one).
What is the solution here, if I want to update the context whenever a page loads?
The correct answer for this issue is not to pass an empty dependency array to useEffect but to wrap your context's mergeData in a useCallback hook. I'm unable to edit your code but you may also need to add dependencies to your useCallback like in my example below
import React, { useState, useCallback } from "react"
const defaultContextValue = {
data: {
// set initial data shape here
menuOpen: false,
},
mergeData: () => {},
}
const Context = React.createContext(defaultContextValue)
const { Provider } = Context
function ContextProviderComponent({ children }) {
const [data, setData] = useState({
...defaultContextValue,
mergeData, // shorthand method name
})
const mergeData = useCallback((newData) {
setData(oldData => ({
...oldData,
data: {
...oldData.data,
...newData,
},
}))
}, [setData])
return <Provider value={data}>{children}</Provider>
}
export { Context as default, ContextProviderComponent }
The selected answer is incorrect because the react docs explicitly say not to omit dependencies that are used within the effect which the current selected answer is suggesting.
If you use es-lint with the eslint-plugin-react-hooks it will tell you this is incorrect.
Note
If you use this optimization, make sure the array includes all values
from the component scope (such as props and state) that change over
time and that are used by the effect. Otherwise, your code will
reference stale values from previous renders. Learn more about how to
deal with functions and what to do when the array changes too often.
https://reactjs.org/docs/hooks-effect.html
Is it safe to omit functions from the list of dependencies? Generally
speaking, no. It’s difficult to remember which props or state are used
by functions outside of the effect. This is why usually you’ll want to
declare functions needed by an effect inside of it. Then it’s easy to
see what values from the component scope that effect depends on:
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
By default, useEffect runs every render. In your example, useEffect updates the context every render, thus trigger an infinite loop.
There's this bit in the React doc:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
So applies to your example:
useEffect(() => {
console.log("CONTEXT DATA WHEN PAGE 2 LOADS:", data)
mergeData({
location,
})
- }, [location, mergeData, data])
+ }, [])
This way, useEffect only runs on first mount. I think you can also leave location in there, it will also prevent the infinite loop since useEffect doesn't depend on the value from context.

Categories