React - Updating data in dispatch from useEffect - javascript

I am trying updating data in dispatch in useEffect but showing warning in console
React Hook useEffect has missing dependencies: 'dispatch', 'id', and 'state.selectedHotel'. Either include them or remove the dependency array react-hooks/exhaustive-deps
code
import { GlobalContext } from "../../../context/globalContext";
const HotelDetail = () => {
const [state, dispatch] = useContext(GlobalContext);
const { id } = useParams();
useEffect(() => {
const hotelData = async () => {
try {
let response = await ServiceGetAllHotels();
let hotel = response.hotels.filter(hotel => {
return hotel.hotelUserName === id;
});
dispatch({
type: "UPDATE",
payload: { selectedHotel: hotel[0] }
});
}catch(){}
};
}, [])
};
But warning message disappear when I add this (below code)
useEffect(() => {
.....
}, [dispatch, state.selectedHotel, id])
I dont understand why this error/warning , why error disappear when I add this ? Please help Can I go with this code?

Its not an error but a warning that can save you from bugs because of useEffect hook not running when it was supposed to.
useEffect hook, by default, executes after:
the initial render
each time a component is re-rendered
Sometimes we don't want this default behavior; passing a second optional argument to useEffect hook changes the default execution behavior of useEffect hook. Second argument to useEffect hook is known as its dependency array that tells React when to execute the useEffect hook.
Run "useEffect" once, after the initial render
We can achieve this by passing an empty array as the second argument to the useEffect hook:
useEffect(() => {
// code
}, []);
This effect will only execute once, similar to componentDidMount in class components.
Run "useEffect" everytime any of its dependency changes
When the code inside the useEffect depends on the state or a prop, you sometimes want useEffect to execute every time that piece of state or prop changes.
How can we tell React to run the effect every time a particular state or prop changes? By adding that state or prop in the dependency array of the useEffect hook.
Example:
Imagine a Post component that receives post id as a prop and it fetches the comments related to that post.
You might write the following code to fetch the comments:
useEffect(() => {
fetch(`/${props.postId}`)
.then(res => res.json())
.then(comments => setComments(comments))
.catch(...)
}, []);
Problem with the above code:
When the Post component is rendered for the first time, useEffect hook will execute, fetching the comments using the id of the post passed in as the argument.
But what if the post id changes or the post id is not available during the first render of the Post component?
If post id prop changes, Post component will re-render BUT the post comments will not be fetched because useEffect hook will only execute once, after the initial render.
How can you solve this problem?
By adding post id prop in the dependency array of the useEffect hook.
useEffect(() => {
fetch(`/${props.postId}`)
.then(res => res.json())
.then(comments => setComments(comments))
.catch(...)
}, [props.postId]);
Now every time post id changes, useEffect will be executed, fetching the comments related to the post.
This is the kind of problem you can run into by missing the dependencies of the useEffect hook and React is warning you about it.
You should not omit any dependencies of the useEffect hook or other hooks like: useMemo or useCallback. Not omitting them will save you from such warnings from React but more importantly, it will save you from bugs.
Infinite loop of state update and re-render
One thing to keep in mind when adding dependencies in the dependency array of the useEffect is that if your are not careful, your code can get stuck in an infinite cycle of:
useEffect --> state update ---> re-render --> useEffect ....
Consider the following example:
useEffect(() => {
const newState = state.map(...);
setState(data);
}, [state, setState]);
In the above example, if we remove the state from the dependency array, we will get a warning about missing dependencies and if we add state in the array, we will get an infinite cycle of state update and re-render.
What can we do?
One way is to skip the state as a dependency of the useState hook and disable the warning using the following:
// eslint-disable-next-line react-hooks/exhaustive-deps
Above solution will work but it's not ideal.
Ideal solution is to change your code in such a way that allows you to remove the dependency that is causing the problem. In this case, we can simply use the functional form of the setState which takes a callback function as shown below:
useEffect(() => {
setState(currState => currState.map(...));
}, [setState]);
Now we don't need to add state in the dependency array - problem solved!
Summary
Don't omit the dependencies of the useEffect hook
Be mindful of the infinite cycle of state update and re-render. If you face this problem, try to change your code in such a way that you can safely remove the dependency that is causing the infinite cycle

The useEffect hook accepts two arguments. The first one is a classic callback and the second one is an array of so called "dependencies".
The hook is designed to execute the callback immediately after component has been mount (after elements have been successfully added to the real DOM and references are available) and then on every render if at least one of the values in the dependencies array has changed.
So, if you pass an empty array, your callback will be executed only once during the full lifecycle of your component.
It makes sense if you think about it from a memory point of view. Each time that the component function is executed, a new callback is created storing references to the current execution context variables. If those variables change and a new callback is not created, then the old callback would still use the old values.
This is why "missing dependencies" is marked as a warning (not an error), code could perfectly work with missing dependencies, sometimes it could be also intentional. Even if you can always add all dependencies and then perform internal checks. It is a good practice to pass all your dependencies so your callback is always up to date.

Related

Why can't we invoke setter functions directly inside components in react? [duplicate]

Let's say I have this simple dummy component:
const Component = () => {
const [state, setState] = useState(1);
setState(1);
return <div>Component</div>
}
In this code, I update the state to the same value as before directly in the component body. But, this causes too many re-renders even if the value stayed the same.
And as I know, in React.useState, if a state value was updated to the same value as before - React won't re-render the component. So why is it happening here?
However, if I try to do something simillar with useEffect and not directly in the component body:
const Component = () => {
const [state, setState] = useState(1);
useEffect(()=>{
setState(1);
},[state])
return <div>Component</div>
}
This is not causing any infinte loop and goes exactly according to the rule that React won't re-render the component if the state stayed the same.
So my question is: Why is it causing an infinte loop when I do it directly in the component body and in the useEffect it doesn't?
If someone has some "behind the sences" explanation for this, I would be very grateful!
TL;DR
The first example is an unintentional side-effect and will trigger rerenders unconditionally while the second is an intentional side-effect and allows the React component lifecycle to function as expected.
Answer
I think you are conflating the "Render phase" of the component lifecycle when React invokes the component's render method to compute the diff for the next render cycle with what we commonly refer to as the "render cycle" during the "Commit phase" when React has updated the DOM.
See the component lifecycle diagram:
Note that in React function components that the entire function body is the "render" method, the function's return value is what we want flushed, or committed, to the DOM. As we all should know by now, the "render" method of a React component is to be considered a pure function without side-effects. In other words, the rendered result is a pure function of state and props.
In the first example the enqueued state update is an unintentional side-effect that is invoked outside the normal component lifecycle (i.e. mount, update, unmount).
const Component = () => {
const [state, setState] = useState(1);
setState(1); // <-- unintentional side-effect
return <div>Component</div>;
};
It's triggering a rerender during the "Render phase". The React component never got a chance to complete a render cycle so there's nothing to "diff" against or bail out of, thus the render loop occurs.
The other example the enqueued state update is an intentional side-effect. The useEffect hook runs at the end of the render cycle after the next UI change is flushed, or committed, to the DOM.
const Component = () => {
const [state, setState] = useState(1);
useEffect(() => {
setState(1); // <-- intentional side-effect
}, [state]);
return <div>Component</div>;
}
The useEffect hook is roughly the function component equivalent to the class component's componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods. It is guaranteed to run at least once when the component mounts regardless of dependencies. The effect will run once and enqueue a state update. React will "see" that the enqueued value is the same as the current state value and won't trigger a rerender.
Similarly you could use the useEffect hook and completely remove the dependency array so it's an effect that would/could fire each and every render cycle.
const Component = () => {
const [state, setState] = useState(1);
useEffect(() => {
setState(1);
});
return <div>Component</div>;
}
Again, the useEffect hook callback is guaranteed to be invoked at least once, enqueueing a state update. React will "see" the enqueued value is the same as the current state value and won't trigger a rerender.
The takeaway here is to not code unintentional and unexpected side-effects into your React components as this results in and/or leads to buggy code.
When invoking setState(1) you also trigger a re-render since that is inherently how hooks work. Here's a great explanation of the underlying mechanics:
How does React.useState triggers re-render?

How does "unsubscribe" in the useEffect cleanup function actually work?

In many code examples I see something like this:
const [items, setItems] = useState([]);
useEffect(() => {
setItems(store.getState().items.length);
const unsubscribe = store.subscribe(() => {
setItems(store.getState().items.length);
});
return unsubscribe; // <-- huh?
}, []);
My question is; how does returning a reference to the subscription unsubscribe from it?
const unsubscribe = store.subscribe(() => {
setItems(store.getState().items.length);
});
This call to store.subscribe immediately creates a subscription with the redux store, and then redux returns a function to you. This returned function is an unsubscribe function which knows how to tear down the subscription. If you're curious, here's the source code where they create that function.
return unsubscribe;
By returning the unsubscribe function, you tell react "hey, when it's time to tear down this effect, please run unsubscribe". React will then call it at the appropriate time: either when the component unmounts, or when the dependencies on the effect change.
.subscribe returns a function that unsubscribes the change listener. That is what you return from useEffect callback and is called as cleanup.
It gets called automatically. So questioning that is like questioning how does useEffect run after renders. React takes care of this so you do not have to worry.
the cleanup function runs before every render. So every render it will run before the useEffect. It will also be executed when the component unmounts, and then of course the rest of the useEffect will not run anymore
The useEffect cleanup function allows applications to prevent having memory leaks by 'cleaning up' any effects. In a basic example you have this structure
useEffect(() => {
effect
return () => {
cleanup
}
}, [input])
Here you will see the useEffect method will run an effect whenever an input in the dependancy array gets updated. So if your useEffect returns a function once it's time to unmount (or update) it'll run that function.
As for how it works think of the logic, when the component mounts we run the code and store the result. When it comes time to unmount or rerender the stored result will run first cleaning up any logic.
Now more specific to your code I don't think it makes sense, you should investigate the store.subscribe method and that will most likely answer your question.
EDIT
after seeing the Link to the code you'll see the initial question had a memory leak in it.

Incrementing state in useEffect sometimes result in a wrong state

I'm creating a simple chat app with React and socketio, here is the useEffect for receiving messages:
useEffect(() => {
socket.on("new-message", () => {
setMessagesCount(messagesCount + 1) //THIS DOESNT WORK
})
}, [])
The problem is that when I increment the messagesCount state, it doesn't increment and sometimes it decrements.
You should use callback form of setState, i.e. line #7 becomes
setMessage(messages => [...messages, data])
Edit: and to count the messages, it should be
setMessagesCount(messagesCount => messagesCount+1);
Reason:
This is because messages is a useEffect dependency and since that hook is set once component mounts and doesn't change for subsequent re-renders, the reference to messages also doesn't change for the hook. The callback form of setState allows us to use latest value of messages without adding messages to that useEffect's dependencies.

How to get the correct state in an useEffect hook, after another useEffect hook modifies the same state?

I have two useEffect hooks, which will both run on change in startTime/endTime state.
useEffect(() => {
if (selectedProduct) {
fetch(...)
.then(res => res.json())
.then(res => setMyState(res.data));
}
}, [selectedProduct, startTime, endTime]);
useEffect(() => {
if(selectedStep){
console.log(myState); //this runs twice, once for the old state and once for the new state, after above useEffect's setState is complete
}
}, [selectedStep, startTime, endTime, myState]);
I understand from looking around that this is probably because React does some sort of batching for performance gain such that setState is async in nature. Also, the fetch call would be async too, so it will take time to set the new state.
From what I have searched, I see that there is a callback for class based React components using setState but as I am using hooks, I am not sure what to do. I can move the second useEffect code in the first one, but that would require some ugly if-else conditions as I need to check if selectedStep has not changed, and then I can use the res.data directly.
Is there a better way of doing this?
It's not batching of operations or anything like that, it's just that that's how it works: When startTime or endTime changes, each of those hooks is executed, and since the first changes myState, your second one gets called again for that change.
A few options for you
Combine the hooks as you described, even if it involves conditional logic.
Depending on what you're doing with myState, you might make the second hook use the callback form of setMyState which receives the current state value as a parameter.
(If possible) Remove the dependencies on other state items in your second useEffect so that it only gets executed when myState changes
Despite your saying it'll involve some if/else, I'd lean toward #1 if you can't do #3.

What React useCallback do when I pass. an object in first argument

I am fixing an infinite loop when using react with useEffect and useCallback.
const fetchApi =useCallback(()=>{
setIsLoading(true)
fetchDataFunction({
resolve(res) {
setFetchData(fromJS(res))
setIsLoading(false)
}
})
},[param])
useEffect(()=>{
fetchApi()
},[fetchApi])
It is causing an infinite loop. However, if I change the code as shown below
const fetchApi = useCallback({
fetchApiFunc() {
setIsLoading(true)
fetchDataFunction({
resolve(res) {
setFetchData(fromJS(res))
setIsLoading(false)
}
})
}
},[params])
const { fetchApiFunc } = fetchApi
useEffect(() => {
fetchApiFunc()
}, [fetchApiFunc])
the problem is fixed. But I still do not understand what it does when the object in first params in useCallback.
Thank you.
If you start using React-Hooks, and your component might need a life cycle method at some point. And, that is when you start using useEffect() (a.k.a Effect Hook). Then boom!!, you have encountered an infinite loop behavior, and you have no idea why the hell is that. If that happens, this article will explain to you why, and how can you prevent.
Where is the problem?
The “useEffect()”, will run after the initial render, then invoke the "fetchApiFunc()”.
Inside the "fetchApiFunc()”, it will update the state “name” on line Then it will trigger the component to re-render again.
As a result, “useEffect()” will run again and update the state. Next, the whole process repeats again, and you're trapped inside an infinite loop.
“You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect”, from the official here: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

Categories