Directly using several states included in useEffect's dependency array - reactjs

Considering an useEffect with 2 different states in the dependency array. The useEffect hook will run whenever any of those two states are updated, but if i update one of them, will i have access to the lastest value of the other inside useEffect? And if not, what is the best approach to it?
function Component() {
const [state1, setState1] = useState('');
const [state2, setState2] = useState('');
useEffect(() => {
console.log(state1, state2)
}, [state1, state2]);
return <>...</>
}

The callback inside useEffect will run after the render conditionally based on dependency array.
If your state values are updated in the same render cycle then they are batched (by React) and the next render cycle will show both the correct values in the useEffect callback.
If you only update any one of them, you do not have to worry about the other value because the callback in useEffect will be using the recently updated value of the other variable too.
Note: The only time you might face an issue is when you have stale state values because of closure, but that is a specific case.

Related

Functional component - calling useEffect on state change, but not when props changed

Working with a functional component which has the following two useEffects:
//update the state when props changed
useEffect(() => {
const newState = mapPropsToState(props);
if (!_.isEqual(newState, state)) {
setState(newState);
}
}, [props]);
//make an API call
useEffect(() => {
preferencesChanged();
}, [state]);
State is derived from props, so the purpose of the first useEffect is to respond to a change in props and update the state.
The purpose of the second useEffect is to make an API call when state has changed. However, this API call can result in the props of this component changing (since preferencesChanged() updates the state of a parent component).
What I really want is for the setState in the first useEffect to be done "quietly" and not to trigger the 2nd useEffect.
Is this possible? Or am I thinking about this design in completely the wrong way?
State is derived from props.
You can just do your logic inside of component (in render phase), instead of calling it in an effect:
//...
const newState = mapPropsToState(props);
// ...
and you can use useMemo if that is an expensive calculation:
const newState = useMemo(() => mapPropsToState(props), [props])
And to answer your question,
What I really want is for the setState in the first useEffect to be done "quietly" and not to trigger the 2nd useEffect.
You can store the relevant info (dependencies of 2nd useEffect) in a seperate state varable, and then use that.
Refs:
You might not need an effect (React docs)
Summing up some of nuances of useEffect

UseEffect triggering without respect to dependency array

I have a function below which i used as an array dependency to a useEffect handler
const handleInputUpdate = (event) => {
const eventValue = event.target.value;
setState({ ...state, answer_text: eventValue, trigger: true })
// console.log("I am changing for no reason")
}
Below is the useEffect handler
useEffect(() => console.log(" I am changing for no reason in useeffect"), [handleInputUpdate])
What i want is the useEffect handler to run only when the handleInputUpdate function is called but it runs also on component mount.
Here's what i've observed
The handleInputUpdate function doesn't run on component mount but only i need it to
Without respect to the above observation, the useEffect handler runs anyway.
Here's what i've tried
I tried consoling a text inside the handleInputUpdate function to see whether it runs on component render but it doesn't.
Even though the function doesn't run, the useEffect handler triggers anyway which is not what i want.
How can i solve this ?
Thanks in advance
useEffect dependency array is not used to trigger the effect when a function is called; the elements of the array are observed for any change and then trigger the effect.
In this case, handleInputUpdate will change on every render because it is not memoised, so the effect will also run on every render.
Since handleInputUpdate changes the state when it is called, you are better off adding that state to your useEffect dependency array:
useEffect(() => {
if (answer_text && trigger) {
console.log("I am changing for a reason in useeffect")
}
}, [answer_text, trigger])
The handleInputUpdate function, while it doesn't run on render, looks like it's created when the component runs, just before rendering. Since it won't be === to the value last in the dependency array - the handleInputUpdate from the prior render - the effect callback will run.
You need to observe changes to the answer_text value in state instead.
useEffect(() => {
// ...
}, [state.answer_text]);
I would also recommend separating out your state into different variables - these aren't class components, don't feel like you have to mash everything together into a single object structure.
const [text, setText] = useState('');

Why doesn't my state change when the category on useParams changes?

const { category } = useParams();
const [sort, setSort] = useState(category);
This think is really driving me crazy - when i console the category, it is actually changing, but sort state doesn't..
You need to do some reading in order to understand how React Hooks work. There are many tutorials online - the React API documentation is even a good start:
What do we pass to useState as an argument? The only argument to the useState() Hook is the initial state.
From the React docs. Notice they say the argument is the initial state only.
When you do useState(X) - the value X is only used the first time the component is rendered. On subsequent renders, if and when X changes, and you want to use the new X, you must update the state value yourself via the set function returned by useState.
In your case, we can say that the state variable changes whenever category changes, so category is a dependency of the state variable. When you have a dependency of this kind, you know you need to use an effect, by way of the useEffect hook:
const { category } = useParams();
const [sort, setSort] = useState(category);
// This callback will run whenever the dependencies
// specified in the second argument change.
useEffect(() => {
setSort(category)
}, [category]) // <= the dependencies
You are expecting whenever category changes, sort should also changes, if this is the goal, try this:
useEffect(() => setSort(category), [category]);
if you only want to change sort first time on initial render, try this:
useEffect(() => setSort(category), []);
For more info, see Using the Effect Hook - React

When updating state from props, why useEffect() instead of checking directly in the component?

When updating state when props change, the commonly held approach is to use useEffect() with the prop as a dependency:
const Component = ({prop}) => {
const [state, setState] = useState(prop);
useEffect(() => {
setState(prop);
}, [prop]);
return <div>{state}</div>;
}
I have to wonder, is there an advantage to this over doing a comparison directly in the component itself, like so:
const Component = ({prop}) => {
const [state, setState] = useState(prop);
if (prop !== state) {
setState(prop);
}
return <div>{state}</div>;
}
It looks like both approaches cause the component to execute twice -- once with the prop and state out of sync, once in sync -- but the second approach looks like it avoids adding a hook to the stack. It also looks like it could be optimized out of the DOM reconciliation, since it doesn't have to wait for the DOM to be generated the way useEffect() does. I'm not even sure it is easier to read, other than being "The Hooks Way."
Does anyone have an idea why the useEffect() route could be better than the inline check?
The official React docs use the second approach for syncing props to state:
https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops
function ScrollView({row}) {
const [isScrollingDown, setIsScrollingDown] = useState(false);
const [prevRow, setPrevRow] = useState(null);
if (row !== prevRow) {
// Row changed since last render. Update isScrollingDown.
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
}
The difference between updating state in useEffect and updating state during render is that, useEffect is called after React already commits the updates, i.e. the updates would be reflected in DOM, then you'll update state which will update the DOM again. The second way causes a re-render, but there's only one commit to the DOM.
I think it's important to understand when to use hooks and when not too.
The answer to this question is a very helpful How does React implement hooks so that they rely on call order.
We have recently discovered a lot of problems to do with the useEffect hook, one of them being
useEffect(() => {
// Called on first render and every props.foo update
}, [props.foo])
We didn't want it to be called on the first render only on every update.
Another on being useEffect gives warning when using objects & arrays, instead of a value.
I'm not saying don't use the useEffect ever I love the useEffect hook, but instead of saying why shouldn't use it. You should say do I need useEffect to get what I need to achieve.
For your example unless you are setting the state at another point I would suggest. Just using the prop. If you are setting the state I would have a look at the above link at the schema found.
The schema of a single hook is as below. It can be found in the implementation
function createHook(): Hook {
return {
memoizedState: null,
baseState: null,
queue: null,
baseUpdate: null,
next: null,
};
}
Think do I need to make use of the hooks queue when setting the state, if not then don't use a hook.
Another good use of the hook is are you going to be putting multiple values in the hook array if so it's a good idea to use it then!
Hope that helps :)

setState in React's useEffect dependency array

What's the idea behind defining the setState values inside useEffect's dependency array?
const [someState, setSomeState] = useState();
...
useEffect(() => {
// Do something
setSomeState('some value');
}, [setSomeState]);
React Hook useEffect has a missing dependency: 'setSomeState'.
Either include it or remove the dependency array.
eslint(react-hooks/exhaustive-deps)
Not exactly sure if this example would cause eslint to ask to define setSomeState in the dependency array, but I have seen this kind of moments when writing with useEffect. What is React listening to in this case? My understanding is that useEffect listens for changes in the values in the dependency array, but would setSomeState ever change? What am I missing here?
In this case, no, the useState setter function will never change, but useEffect doesn't know it is a function from another hook, it just sees a function.
Consider this use case:
const MyComponent = ({ onUpdate }) => {
const [state, setState] = useState();
useEffect(() => {
// state updated, call onUpdate callback with state
onUpdate(state);
}, [onUpdate, state]);
return ...
}
Now parent component can, given
<MyComponent onUpdate={onUpdateHandler} />
define onUpdateHandler as
let onUpdateHandler = console.log;
then later change it while still mounted
onUpdateHandler = state => dispatch({ type: 'SAVE_STATE', state });
By placing the callback in the dependency array the effect hook see the value of onUpdate changed and trigger the effect again. If it didn't include the callback then it would continue to console.log state updates instead of now handling it differently.
It is more likely for values to change than it is for functions to change, but function values can change. You likely have an eslinter that is recommending you add all the variables used within the hook to the dependency array, and this will in most cases be the correct thing to do. A few exceptions are for example, when you want an effect to compute ONLY when the component mounts by specifying an empty array, or not including some variables that are functions that you KNOW will never change during the life of the component, and in these cases you can use the eslint-disable-next-line react-hooks/exhaustive-deps override to unflag it as a warning. Caution though, as this will disable the linting checks so it is possible for bugs to creep in if not paying attention.

Resources