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

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

Related

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.

React - Updating data in dispatch from useEffect

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.

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.

React - Effect of closures within React application

I have stumbled upon an issue that is very weird for me, but most probably is very simple to explain.
Demo
Let's assume the following React component
import React, { useState, useEffect, useCallback } from "react";
export default function App() {
const [test, setTest] = useState();
const doSomething = () => {
// TODO: Why does this returns the inital state value? Hoisting?
console.log(test);
};
const doSomethingWithCallback = useCallback(doSomething, [test]);
useEffect(() => {
setTest("asas");
window.setTimeout(() => doSomething(), 2000);
document.addEventListener("click", doSomethingWithCallback);
return () => {
document.removeEventListener("click", doSomethingWithCallback);
};
}, [doSomethingWithCallback]);
return (
<div className="App">
<h1>Click anywhere</h1>
</div>
);
}
(cf. CodeSandbox)
Question
Look at the TODO comment in the code.
Why does doSomething console logs the state test as it is initially is set, namely undefined whereas the callback variant is returning it with the "real" current state when it is called?
Is this some kind of hoisting or performance optimization React is doing?
This doesn't have much of anything to do with hoisting. setTest is causing a rerender which will cause the useCallback to evaluate it's callback since it's dependency changed.
Explaining why the other function call returns the initial value is a little more mysterious, but I think it has to do with the value of test when you registered the call to setTimeout.
Any setState is asynchronous, you can not guarantee the value in state will line up with what you expect if you set the state and then synchronously try to reference state right afterwards. To the contrary, it most likely won't, I'm not fully informed, but I believe more or less no asynchronous work is done until the queue of synchronous work is completely empty.
As for your actual question, "are state values hoisted?" yes they are, like all values in Javascript. However react state hook values differ slightly in that the order of their execution must always be the same between renders, so you have to maintain at least that much (i.e. no conditional hook instantiation may exist).

In React after calling setState, are there hard and fast rules for avoiding asynch errors when accessing/changing state?

I am learning React and this is my first time handling asynchronous operations. Currently I am building a simple calculator and have run into some minor issues after using setState and then immediately trying to access the state. It's been easy to work around the issues because my app is pretty simple. However, I haven't been able to find definitive rules for how long it takes after calling setState to be certain that state has actually been updated. Does it require a certain amount of time? Is it dependent on the structure of my code? Also, is it appropriate to use setTimeout after setState in order to give state time to update? If anyone could give me some insight into when React will update the state and save me the guesswork, it would be appreciated.
Does it require a certain amount of time?
No specific amount.
Is it dependent on the structure of my code?
In a general way, but that's not really the question. :-)
Also, is it appropriate to use setTimeout after setState in order to give state time to update?
No.
In React after calling setState, are there hard and fast rules for avoiding asynch errors when accessing/changing state?
Yes. They're covered in the documentation, particularly here. They are (at minimum):
The only way to know state has finished changing is to use componentDidUpdate or the completion callback you can provide to setState (the second argument):
this.setState(newState, () => {
// `this.state` has the updated state
});
If you're changing state based on existing state, you must use the form of setState that accepts a function as its first argument and calls it with the up-to-date state for you to change.
(Not async specific.) Never directly modify the state object or any object it refers to.
So for example: Suppose you have an array (items) and you need to push an entry into it if it's not already there:
// WRONG, breaks Rule #3
if (!this.state.items.includes(newItem)) {
this.state.items.push(newItem);
}
// WRONG, breaks Rule #2
if (!this.state.items.includes(newItem)) {
this.setState({items: [...items, newItem]});
}
// WRONG, breaks Rule #1
this.setState({items: [...items, newItem]});
doSomethingWith(this.state.items);
Instead:
this.setState(
({items}) => {
if (!items.includes(newItem)) {
return {items: [...items, newItem]};
}
},
() => {
doSomnethingWith(this.state.items);
}
);
But note that using the completion callback (the second function above) is usually an anti-pattern; really the only thing a component should do when state updates is re-render, and React will call render for you.
But this answer is not a substitute for reading through the documentation. :-)
setState takes a callback as second parameter:
this.setState({
foo: 'bar'
}, () => {
// This code will be executed after setState has been processed
});
Ideally, your components don't care about the timing of a setState update.
Most likely what you are looking for is the setState call that is based on the previous state...
this.setState((prevState) => {
return {
// I almost always just spread the state onto a new object.
...prevState,
// Then you'll want to add your updated properties below. This overrides the old state that you set above
someProp: newVal
};
});
From there, again, your components shouldn't care about when React actually performs the update! (As with all things there are exceptions of course)
If you want to access the state after setState() you can do that in the callback function for the same to verify the actual state is being updated.
Like this:
this.setSate ({
test : 'Hello Word'
}, () => {
console.log('this.state', this.state)
})
Whenever the state is changed, it compares the DOM and virtual DOM to find out differences, and renders them on the actual DOM.
https://reactjs.org/docs/state-and-lifecycle.html

Categories