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.
Related
I have been using these two ways interchangeably however I am not sure which one is the more correct. their behavior seems to be the same but I am sure there is a use case for each. Anyone can help me understand what's the proper use for each case?
const [customer, setCustomers] = useState(props.location.state);
useEffect(() => {
setCustomers(props.location.state);
}, []);
You should normally stick to the first one. Calling the setter of useState may lead to undesired re-renders and decreased performance.
In the first block the customer is initialised directly and no re-render happens. The setCustomer method will change the state and rerender the component. In the end the whole function will run twice which you can verify with a console.log.
const [customer, setCustomers] = useState(0);
useEffect(() => {
setCustomers(15);
}, []);
console.log(customer) // will first output 0 and then 15
Assuming in the second case, you have this as your useState statement:
const [customer, setCustomers] = useState();
The second one sets the value of customer on componentDidMount. So in the initial render, you will not have the appropriate value in your customer variable.
But yes, very soon after that the correct value will be set because of the code written in useEffect.
To clear it up, there will be 2 renders here (because the state variable value changes). In the first one, that won't be the case since the state variable has only one value from beginning.
The first one is more effective.
const [customer, setCustomers] = useState(props.location.state);
If you use second one (by using useEffect), your component will be re-rendered again.
That's, your state variable customer will be updated in useEffect after DOM is initially rendered, this leads the 2nd re-render of the component.
But if you want customer to be updated by props.location.state, you need to add useEffect hook like the following.
useEffect(()=> {
setCustomers(props.location.state);
}, [props.location.state]);
Setting the state's default value upon declaring it is probably the more correct way to go, since it does not trigger a re-render.
Every time you call a setState your component will be re-rendered, so when you do so in the useEffect, you will trigger an unnecessary re-render upon the component mounting, which could be avoided by doing the good ol'
const [value, setValue] = useState(props.location.state)
While of course there are exceptions and many different use cases, setting an initial state in a useEffect is more useful, for example, when you have values you'd expect to change regardless of your component (for example from an external asynchronous API call):
const [value, setValue] = useState(valueExpectedToChange)
useEffect(() => {
setValue(valueExpectedToChange) // will trigger the rerender only when valueExpectedToChange changes
}, [valueExpectedToChange])
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
Is useState's setter able to change during a component life ?
For instance, let's say we've got a useCallback which will update the state.
If the setter is able to change, it must be set as a dependency for the callback since the callback use it.
const [state, setState] = useState(false);
const callback = useCallback(
() => setState(true),
[setState] // <--
);
The setter function won't change during component life.
From Hooks FAQ:
(The identity of the setCount function is guaranteed to be stable so it’s safe to omit.)
The setter function (setState) returned from useState changes on component re-mount, but either way, the callback will get a new instance.
It's a good practice to add state setter in the dependency array ([setState]) when using custom-hooks. For example, useDispatch of react-redux gets new instance on every render, you may get undesired behavior without:
// Custom hook
import { useDispatch } from "react-redux";
export const CounterComponent = ({ value }) => {
// Always new instance
const dispatch = useDispatch();
// Should be in a callback
const incrementCounter = useCallback(
() => dispatch({ type: "increment-counter" }),
[dispatch]
);
return (
<div>
<span>{value}</span>
// May render unnecessarily due to the changed reference
<MyIncrementButton onIncrement={dispatch} />
// In callback, all fine
<MyIncrementButton onIncrement={incrementCounter} />
</div>
);
};
The short answer is, no, the setter of useState() is not able change, and the React docs explicitly guarantee this and even provide examples proving that the setter can be omitted.
I would suggest that you do not add anything to the dependencies list of your useCallback() unless you know its value can change. Just like you wouldn't add any functions imported from modules or module-level functions, constant expressions defined outside the component, etc. adding those things is just superfluous and makes it harder to read your handlers.
All that being said, this is all very specific to the function that is returned by useState() and there is no reason to extend that line of reasoning to every possible custom hook that may return a function. The reason is that the React docs explicitly guarantee the stable behavior of useState() and its setters, but it does not say that the same must be true for any custom hook.
React hooks are still kind of a new and experimental concept and we need to make sure we encourage each other to make them as readable as possible, and more importantly, to understand what they actually do and why. If we don't it will be seen as evidence that hooks are a "bad idea," which will prohibit adoption and wider understanding of them. That would be bad; in my experience they tend to produce much cleaner alternatives to the class-based components that React is usually associated with, not to mention the fact that they can allow organizational techniques that simply aren't possible with classes.
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.
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.