I'm fetching data from my backend api when component mount and can do it successfuly but my React app keeps sending requests to the server causing it to slow down. I used useEffect hook, but I'm getting the same result without using the hook.
useEffect(() => {
axios.get('http://127.0.0.1:8000/food_category/')
.then(response => {
setFoodCategory(response.data);
console.log(response.data);
})});
What am I doing wrong?
If you give no dependencies to the useEffect hook, it will execute every time your component renders (which will happen infinitely because you set the state after getting data and thus your component rerenders).
Check out the second argument of useEffect in the docs to learn more about it.
An empty dependencies array indicates the useEffect will act as a mount and only executes once.
useEffect(() => {
// Do mount stuff here such as executing your request.
}, []);
Related
On the screen I get : Hello { "name" : "Jack" }
In console I get:
App rendering
useFetch useEffect
App rendering
Because what I have seen in the console , I think App.js must have ran 2 times and useFetch must have ran 1 time .
I think this is behind :
App.js calls useFetch and writes on console that App rendering . App.js does NOT wait useFetch to complete, so Hello undefined is on the screen . BUT we don't see undefined on the screen , because it is so fast and it goes away -> because of 2. step :
While App.js writes on console and displays undefined on screen , useFetch is running . When useFetch completes its running it gives back the correct object ( Jack )
App.js component runs again and writes again on console and displays the correct object ( Hello Jack )
But if this is true , then why runs App.js 2 times ?
Somebody said that my theory about this is false and useFetch gives back 2 times value . First it gives back null as object and second time the correct value , which is from fetch . So he said that useFetch does not wait fetch function to complete , so it gives back first time null and when fetch function completes then it gives back Jack .
But if he has right , then why is that ? Why does not wait useFetch to complete its functions to complete ?
Can somebody write step by step whats happening behind ?
App.js :
import { useFetch } from './useFetch';
function App() {
const { data } = useFetch({ url : "jack.json" })
console.log('App rendering')
return (
<div className="App">
<div>Hello</div>
<div>{JSON.stringify(data)}</div> )
}
export default App;
useFetch.js :
import { useState, useEffect } from "react";
export const useFetch = (options) => {
const [data, setData] = useState(null);
useEffect(() => {
console.log("useFetch useEffect");
fetch(options.url)
.then( response => response.json())
.then( json => setData(json))
}, [])
return { data }
}
My Question inspired this :
https://youtu.be/dH6i3GurZW8?t=316
Your thinking is correct.
But if this is true , then why runs App.js 2 times ?
Because App is a react component and like all other react components it will re-render whenever its data dependencies change. In your case, App will re-render whenever the value of data changes.
Your custom useFetch hook uses the useEffect hook to fetch data. Effects in React are performed after react components have rendered. That's why the console logs the first "App rendering". Once the useFetch hook calls setData, the value of data is updated which triggers the second re-render of App.
Here is a step-by-step of what's happening:
function App() {
const { data } = useFetch({ url : "jack.json" })
console.log('App rendering')
return (
<div className="App">
<div>Hello</div>
<div>{JSON.stringify(data)}</div> )
}
Before I begin, it may help to also understand how the react component lifecycle works:
From the start, it's just like calling a regular function...
If you refer to the diagram above, we are at the stage called render, because the body of a functional component is the equivalent of the render function in class components.
First, useFetch is called
const { data } = useFetch({ url : "jack.json" });
Inside useFetch
const [data, setData] = useState(null);
At this point, data is null
Next useEffect is called
useEffect(() => {
console.log("useFetch useEffect");
fetch(options.url)
.then( response => response.json())
.then( json => setData(json))
}, []);
useEffect creates an effect watcher. This is basically a function which is invoked every time one of its dependencies changes. However, it is not invoked until after the first time the component renders.
If you refer to the diagram above, the watcher will only be invoked for the first time at the point called componentDidMount.
Back inside App:
(image reposted to avoid scrolling)
console.log('App rendering')
We get a nice console.log output...
return (
<div className="App">
<div>Hello</div>
<div>{JSON.stringify(data)}</div>
);
Here we return control back to react, and it will now attempt to take our html and mount it inside the dom.
Note that data is still null. Every other component react encounters will go through the same process described above.
So far, the steps leading up to this point can be summarized as Mounting (See the diagram above), and we have now reached the componentDidMount stage.
componentDidMount
React will now invoke our effect.
Note: If you had more than one effect, react will invoke all of them, and none will be skipped for this first round of effectual work that react does. Also they will all be invoked in the same order you declared them in.
useEffect watcher invoked
console.log("useFetch useEffect");
Another nice printout to console.log
fetch(options.url)
.then( response => response.json())
.then( json => setData(json))
fetch is invoked, but since we are not allowed to wait for promises inside effects, the fetch just runs.
Note: If your effect returns any function, react saves it to run it the next time an update/unmount occurs.
At this point, we just kinda idle until something interesting hap...oh wait fetch is done.
State Updates (setData(...))
(image reposted to avoid scrolling)
.then( json => setData(json))
setData will update the state of the component. If you refer to the diagram above once again, you'll see that when a state update occurs, the next step is to render.
Therefore, react will once again repeat the same steps as above, but it does not re-create the useEffect, or the useState again, because it has stored them from the previous render.
Sidenote: This is also how react is able to detect when you are calling a hook (useEffect, useState, etc) conditionally. If react detects a new hook after the first render is finished, it will warn you of this
App is rendered again
console.log('App rendering')
Next...
return (
<div className="App">
<div>Hello</div>
<div>{JSON.stringify(data)}</div>
);
And we once again return the html content of our component (this time data is now whatever was sent to setData).
Updating
As you can tell, we are now in the Updating Phase. From now on, react will simply wait for props/state updates, and runs any effects that depend on them in the componentDidUpdate stage of the lifecycle
Sidenote: Your useEffect should depend on options.url so that it is re-run when the url changes
useEffect(() => {
console.log("useFetch useEffect");
fetch(options.url)
.then( response => response.json())
.then( json => setData(json))
}, [options?.url]);
Unmounting
If by some bad luck, your component is unmounted either by the parent, or by some strange magic, your component will be moved into the Unmounting phase. Once again, refer to the diagram above
In this phase, none of the effects are run. Instead, any functions returned by your "effect watcher", will be invoked to do cleanup.
Note: All the cleanup functions will be run in the order they were encountered (typically top-down)
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.
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.
I'm trying to build a Update profile form where a user can change his details and onSubmit the new data will be sent to PUT/users end point to update the details in the database. The form will have the old values pre filled (gotten through a GET request to the same /users endpoint). To access this endpoint we also need to send a basic auth header with email and password. Now I'm making two fetch requests one to get the existing details and one to PUT the new details. My first GET request is successfully made and I can see the data prefilled but my second POST request doesn't work. What am I doing wrong?
Here is how my code looks. https://codesandbox.io/s/dawn-breeze-itinq?file=/src/App.js
const getUsers = async () => {
let myHeaders = new Headers();
myHeaders.set('Authorization', 'Basic ' + credentials);
const requestOptions = {
method: 'GET',
headers: myHeaders,
redirect: 'follow',
};
try {
const response = await fetch(`${APIlink}/users`, requestOptions);
const result = await response.json();
setData(result);
} catch (err) {
setErr('Incorrect Password. Please Retry.');
}
};
useEffect(() => {
getUsers();
}, []);
You useEffect gets called on Every render as you have not mentioned any dependency array. So what happens in your case is,
your component renders --> useEffect gets called --> make api call --> you set state --> component re-renders --> useEffect gets called --> make api call and this cycle continues forever .
useEffect(() => {
....
}); => this useEffect will trigger on first render and every re-render
useEffect(() => {
...
}, []); => this will trigger only for the first time when the component is mounted
useEffect(() => {
...
}, [value]); => triggers on first render and whenever the value changes
If you are familiar with lifecycle methods in class based components, you usually do API calls in componentDidMount lifecycle method. This method is called only once after the first render. More on this: https://reactjs.org/docs/react-component.html#componentdidmount
In your code, you are using useEffect which is more or less like the componentDidUpdate lifecycle method, which is called on every render. this is why,
The page loads and I can see an endless string of GET requests in the network tab.
More on this: https://reactjs.org/docs/react-component.html#componentdidupdate
The solution would be to make your useEffect hook behave like a componentDidMount lifecycle method, which is essentially telling the useEffect hook to run only once.
If we examine a hook, it is made up of two components:
Callback function (required)
Dependency list of props (optional)
which looks like this:
useEffect(Callback, [Dependencies]);
An example would be:
useEffect(() => {
console.log(props.color)
}, [props.color]);
So essentially we are saying that whenever the prop color is changed, we want to log the value of color.
Seeing as how this is, if we pass an empty array of dependencies, the useEffect hook will only run once and that is what you should do. Do note that if a dependencies array is not passed, it will still behave like componentDidUpdate lifecycle method i.e it will be executed on every render.
Solution if you just skipped to this part is:
Pass an empty dependencies list in your useEffect hook.
More on hooks:
https://reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update
https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
I'm working with Material-UI components on the project and is using AutoComplete component in my app.
In the example from Material-UI team, I came across an interesting example of the AutoComplete Ajax data: https://material-ui.com/components/autocomplete/#asynchronous-requests
They are using React Hooks to fetch the data from the server:
React.useEffect(() => {
let active = true;
if (!loading) {
return undefined;
}
(async () => {
const response = await fetch('https://country.register.gov.uk/records.json?page-size=5000');
await sleep(1e3); // For demo purposes.
const countries = await response.json();
if (active) {
setOptions(Object.keys(countries).map((key) => countries[key].item[0]));
}
})();
return () => {
active = false;
};
}, [loading]);
Why do we use active variable here? Why we return a function that changes this variable to false? It is always true in the if-statement.
Thanks in advance for the answer
The function returned from useEffect is a cleanup function. It is called when the component un-mounts - and is usually used to unsubscribe to events, cancel pending promises etc that were used in the useEffect.
The active variable is used make sure that you aren't updating the state on something that doesn't exist anymore. It's somewhat like the isMounted anti-pattern that existed in class components.
When you try to update state on an un-mounted component, React will throw a warning -
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application.
Having the active variable prevents that in the following way:
Your component loads
The useEffect calls an async fetch - this will take time
Now say, before the server from the response is returned, you navigate away from the page (or perform some other action that un-mounts the component)
That will cause the component to unmount and the cleanup function to be called:
return () => {
active = false;
};
active is now set to false
Finally, we get our response from the server. And now, it'll encounter the false active value, and not update the state.
// active === false,
// this will skip `setOptions`
if (active) {
setOptions(...);
}
This is a pattern which is used to avoid two situations:
updating the state if component containing this hook has already unmounted before HTTP request could complete.
avoid race conditions
Function returned by callback function of useEffect hook is used to perform the clean up, like componentWillUnmount in class based components. This cleanup function runs when the component unmounts or before running the effect next time.
So if component is unmounted when the HTTP request was in progress, cleanup function of useEffect hook will run and will set active to false. After that, whenever the result of HTTP request returns, state won't be updated because active will be false.
See Effects with cleanup section of react docs to understand the cleanup function of useEffect hook and see Race Conditions to better understand the problem related to race conditions that is solved here by using active variable.