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
Related
I have the following component where within the useEffect, I am calling some data reading related
functions meant to happen once on load.
The problem is, some of the prop data are not available at this stage (still undefined) like the prodData and index.
They are only available when I get into the Nested components like <NestedComponent1 />.
I wish to move this logic into the nested components which will resolve this issue.
But I do not want to repeat these code inside the useEffect for each component. Instead looking to write these 7 lines once maybe in a function
and just call it with the 3 NestedComponents.
Issue is that there is a higher order function wrapping here plus all the values like prodData and index is coming from Redux store.
I can't just move all these logic inside useEffect into a normal JS function and instead need a functional component for this.
And if I make a functional component to perform these operations, I can't call it in the useEffect for each of the NestedComponents.
Cos this is not valid syntax.
React.useEffect(() => {
<NewlyCreatedComponentWithReadingFunctionality />
}, []);
Thus my query is, is there a way I could write a functional component which has the data reading logic inside its useEffect.
And then extend this functional component for each of the functional components so that the useEffect would just fire
when each of these NestedComponents are called?
Doesn't seem to be possible to do this thus looking for alternatives.
This is the existing component where some of these prop values are undefined at this stage.
const MyComponent = ({
prodData,
index,
country,
highOrder: {
AHigherOrderComponent,
},
}) => {
// this is the logic which I am looking to write once and be
// repeatable for all the NestedComponent{1,2,3}s below.
React.useEffect(() => {
const [, code] = country.split('-');
const sampleData = prodData[index].sampleData = sampleData;
const period = prodData[index].period = period;
const indication = prodData[index].indication = indication;
AHigherOrderComponent(someReadDataFunction(code, sampleData));
AHigherOrderComponent(someReadDataFunction(code, period);
AHigherOrderComponent(someReadDataFunction(code, indication);
}, []);
return (
{/* other logics not relevant */}
<div>
<div>
<NestedComponent1 />
<NestedComponent2 />
<NestedComponent3 />
</div>
</div>
);
};
export default connect( // redux connect
({
country,
prodData,
index,
}) => ({
country,
prodData,
index,
})
)(withHighOrder(MyComponent));
React components implement a pattern called composition. There are a few ways to share state between parts of your React application but whenever you have to remember some global state and offer some shared functionality, I would try and manage that logic inside a context provider.
I would try the following:
Wrap all your mentioned components inside a context provider component
Offer the someReadDataFunction as a callback function as part of the context
Within your provider, manage react state, e.g. functionHasBeenCalled that remembers if someReadDataFunction has been called already
Set functionHasBeenCalled to true inside someReadDataFunction
Call someReadDataFunction inside your components within a useEffect based on the props data
This way, your application globally remembers if the function has been executed already but you can still use the latest data within your useEffect within your components to call someReadDataFunction.
Suppose we have an input for buyer id and we want to fetch the buyer details each time the buyerId is changed.
The following code looks like this for class components
componentDidUpdate(prevProps,prevState) {
if (this.state.buyerId !== prevState.buyerId) {
this.props.fetchBuyerData(this.state.buyerId); // some random API to search for details of buyer
}
}
But if we want to use useEffect hook inside a functional component how would we control it. How can we compare the previous props with the new props.
If I write it as per my understanding it will be somewhat like this.
useEffect(() => {
props.fetchBuyerData(state.buyerId);
}, [state.buyerId]);
But then react's hooks linter suggests that I need to include props into the dependency array as well and if I include props in the dependency array, useEffect will be called as soon as props changes which is incorrect as per the requirement.
Can someone help me understand why props is required in dependency array if its only purpose is to make an API call.
Also is there any way by which I can control the previous state or props to do a deep comparison or maybe just control the function execution inside useEffect.
Deconstruct props either in the function declaration or inside the component. When fetchBuyerData is used inside the useEffect hook, then only it needs to be listed as a dependency instead of all of props:
// deconstruct in declaration
function MyComponent({ fetchBuyerData }) {
useEffect(() => {
// caveat: something is wrong with the use of `this.state` here
fetchBuyerData(this.state.buyerId);
}, [fetchBuyerData, state.buyerId]);
}
// deconstruct inside component
function MyComponent(props) {
const { fetchBuyerData } = props;
useEffect(() => {
// caveat: something is wrong with the use of `this.state` here
fetchBuyerData(this.state.buyerId);
}, [fetchBuyerData, state.buyerId]);
}
I'd assume you're rewriting your class component info functional one. Then you'd be better off including your fetch request right where you set new state.bayerId (I assume it's not an external prop). Something like:
const [buyerId, setBuyerId] = React.useState('')
const handleOnChange = (ev) => {
const { value } = ev.target
if (value !== buyerId) {
props.fetchBuyerData(value)
setBuyerId(value)
}
...
return (
<input onChange={handleOnChange} value={buyerId} />
...
The code snippet is somewhat suboptimal. For production I'd assume wrap change handler into useCallback for it to not be recreated on each render.
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.
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.