how to load with useEffect [duplicate] - reactjs

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 1 year ago.
I try to give data with param using Route
and using param, get data from server and print it.
but useEffect doesn't work
export default function Board({ match }) {
const [content, setContent] = useState([]);
useEffect(() => {
getOnePost(match.params.number).then((response) => {
setContent(response);
});
console.log(content);
}, []);
return (
<div>
<div>hi</div>
</div>
);
}
<PrivateRoute
path="/board/:number"
authenticated={this.state.authenticated}
component={Board}
/>

I think you need to receive the url parameters from the useParams hook.
import { useParams } from "react-router-dom";
export default function Board() {
...
const { number } = useParams(); // get url parameters
useEffect(() => {
getOnePost(number).then((response) => {
setContent(response);
});
console.log(content);
}, []);
...
}

I think getonepost function is not defined as a async function. Please use this code.
useEffect(() => {
getOnePost(match.params.number).then((response) => {
setContent(response);
console.log(content);
});
}, []);
or you can try this either.
const [ update, setUpdate]=useState(true);
useEffect(() => {
console.log(content);
});
}, [update]);
getOnePost(match.params.number).then((response) => {
setContent(response);
setUpdate(!update);
}

You should pass match.params.number to useEffect as second argument.
useEffect(() => {
getOnePost(match.params.number).then((response) => {
setContent(response);
});
console.log(content);
}, [match.params.number]);

getOnePost is an asynchronous promise. So attempting to console.log(content) on the next line will not wait for the promise to resolve, and will log the initial value of [].
This is expected behaviour.
If you really want to log the value of content when it changes, add another useEffect with a dependency on content and put the console.log there.
export default function Board({ match }) {
const [content, setContent] = useState([]);
useEffect(() => {
getOnePost(match.params.number).then((response) => {
setContent(response);
});
}, []);
useEffect(() => {
console.log(content);
}, [content])
return (
<div>
<div>hi</div>
</div>
);
}

see, you are using console.log outside the .then statement, as you are writing asynchronous code it will console.log the content first then make a request to the server. if you want to see response, console.log it inside the then statement like this:-
const [content, setContent] = useState([]);
useEffect(() => {
getOnePost(match.params.number).then((response) => {
setContent(response);
console.log(response);
});
}, []);
and if you want to check weather it is stored in state or not you can do this:-
const [content, setContent] = useState([]);
useEffect(() => {
getOnePost(match.params.number).then((response) => {
setContent(response);
});
}, []);
useEffect(() => {
console.log(content)
}, [content])
the first useEffect run to get data from api and second run when content state changes.

Related

ReactJS : Failing to fetch an API

Hey I'm trying to fetch an API, but it dosnt returns anything.
I've checked and I cannot access my pre-built values inside my fetch.
How can I access my values inside the fetch ?
import React, { useState, useEffect } from 'react';
function App() {
const [ positionLat, setPositionLat ] = useState('') ;
const [ positionLong, setPositionLong] = useState('') ;
navigator.geolocation.getCurrentPosition(function(position) {
setPositionLat(position.coords.latitude);
setPositionLong(position.coords.longitude);
});
console.log(positionLat) // returns good result
console.log(positionLong) // returns good result
// I obviously need to call those values inside my fetch
useEffect(() => {
console.log(positionLat) // returns undefined
console.log(positionLong) // returns undefined
fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${positionLat}&lon=${positionLong}&appid={api_key}b&units=metric`)
.then(res => {
return res.json();
})
.then(data => {
console.log(data)
})
}, []);
return (
<div className="App">
<p>lattitude :{positionLat}</p>
<p>longitude :{positionLong}</p>
</div>
);
}
export default App;
One option is to change your effect hook to only run the main body once the values are defined:
useEffect(() => {
if (positionLat === '' || positionLong === '') {
return;
}
// rest of function
fetch(...
}, [positionLat, positionLong]);
You also need to fix your geolocation call to occur only once, on mount.
useEffect(() => {
navigator.geolocation.getCurrentPosition(function(position) {
setPositionLat(position.coords.latitude);
setPositionLong(position.coords.longitude);
});
}, []);
Another option is to split it up into two components, and only render the child component (which does the fetching) once the geolocation call is finished, which might look cleaner.
const App = () => {
const [coords, setCoords] = useState();
useEffect(() => {
navigator.geolocation.getCurrentPosition(function (position) {
setCoords(position.coords);
});
}, []);
return coords && <Child {...coords} />;
};
const Child = ({ latitude, longitude }) => {
useEffect(() => {
fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid={api_key}b&units=metric`)
.then(res => res.json())
.then(data => {
// do stuff with data
})
// .catch(handleErrors); // don't forget to catch errors
}, []);
return (
<div className="App">
<p>latitude :{latitude}</p>
<p>longitude :{longitude}</p>
</div>
);
};

useEffect second argument infinity render

I'm calling a function (getEmployees(url)), inside a useEffect without a second argument.
I want to call the getEmployees(url) every time an employee is added.
As soon as I add an employee or error as a second argument, the useEffect re-renders infinitely.
Is this how its supposed to work?
import React, { useEffect, useState, useCallback } from 'react'
//
import EmployeeRecord from './EmployeeRecord'
const Employees = () => {
const [loading, setLoading] = useState(false);
const [employees, setEmployees] = useState([]);
const [error, setError] = useState({show: false, msg: ''});
//
const url = 'http://localhost:3001/';
//
// const fetchDrinks = useCallback( async () => {
const getEmployees = useCallback( async (url) => {
setLoading(true)
try {
const response = await fetch(url)
const data = await response.json()
if (data) {
setEmployees(data)
setError({ show: false, msg: '' })
} else {
setError({ show: true, msg: data.Error })
}
setLoading(false)
} catch (error) {
console.log(error)
}
}, []);
useEffect(() => {
getEmployees(url)
}, [])
console.log("11111111 from employee.js ")
if (loading){
return (
<div>
.....is loading
</div>
)
}
return (
<div>
<div className="addinfo-infomations">
<EmployeeRecord employees={employees}/>
</div>
</div>
)
}
export default Employees
The second argument of useEffect is a dependency list telling React to call your useEffect function again any time one of the dependencies changes.
If there are no dependencies ([]), it will just get run when the component mounts.
Right now, if you put employees as a dependency, it will change every time your employees array gets set. And, you can see that in your getEmployees function, you call setEmployees. That's how you end up in your loop:
useEffect -> getEmployees -> setEmployees -> (employees changes, triggering useEffect again)
In order to avoid this, you have to not form a loop, or short-circuit it somehow.
You say that you want to run getEmployees every time an employee is added, but there's no code from what you've shown dealing with adding an employee. So, I'm assuming maybe this happens on the server that you're polling? If so, you'll need to find some way to get a notification from the server -- it won't be a matter of just calling useEffect, because React will have no way to know that there's been an employee added. Or, your example is missing some relevant code.
call getEmployees cause employees and error change. If you add an employee or error as a second argument, it means useEffect will execute its callback when employees and error changed. As a result, useEffect will execute its callback when you call getEmployees. Then the callback of useEffect will call getEmployees. infinity happened.
I think if you want reload the empolyees, you can add a refresh button
const [refresh, setRefresh] = React.useState(true);
const doRefresh = React.useCallback(() => {
setRefresh(pre => !pre);
}, []);
useEffect(() => {
...
}, [refresh]);
return (
<div>
...
<button onClick={doRefresh}>reload</button>
</div>
)
or automattically reload
useEffect(() => {
let handle;
const task = () => {
getEmpolyees().then(() => {
handle = setTimeout(task, 1000)
});
}
task();
return () => {
handle && clearTimeout(handle);
}
}, [])
I feel like the employees that are added vs the data you get back from the fetch should be different and if they are different you need to separate them, in order for that to work the way you have it now. Adding another state for it would be enough, lets say we call it setData since you have it named data. You would now setData instead of employees that way the effect doesn't start an infinite loop and then update the effect with all of its dependencies and you'll have no prob.
const Employees = () => {
const [loading, setLoading] = useState(false);
const [employees, setEmployees] = useState([]);
const [data, setData] = useState();
const [error, setError] = useState({ show: false, msg: '' });
//
const url = 'http://localhost:3001/';
//
// const fetchDrinks = useCallback( async () => {
const getEmployees = useCallback(async url => {
setLoading(true);
try {
const response = await fetch(url);
const data = await response.json();
if (data) {
setData(data)
setError({ show: false, msg: '' })
} else {
setError({ show: true, msg: data.Error });
}
setLoading(false);
} catch (error) {
console.log(error);
}
}, []);
useEffect(() => {
url && getEmployees(url);
}, [url, employees, getEmployees]);
if (loading) {
return <div>.....is loading</div>;
}
return (
<div>
<div className="addinfo-infomations">
<EmployeeRecord employees={employees} data={data} />
</div>
</div>
);
};

useEffect causing an endless loop

i have a simple state and a function that runs in useEffect,
i setup the useEffect second argument to my updating state but seems to run in an endless loop
causing endless re-renders
const [file, setFile] = useState({audioFile: {} })
const loadAudioFromPath = (path) => {
import(`../components/Media/Resources/${path}`).then(audio =>
setFile({
audioFile: new Audio(audio),
})
);
}
useEffect(() => {
loadAudioFromPath(resourceURI)
console.log(file)
}, [file])
i also tried
useEffect(() => {
loadAudioFromPath(resourceURI)
console.log(file)
}, [])
still same issue!
EDIT: Try and change your useEffect to something like this:
Basically, add a boolean. If the boolean state changes then useEffect will fire.
const [file, setFile] = useState({audioFile: {} })
const [bool, setBool] = useState(false)
const loadAudioFromPath = (path) => {
import(`../components/Media/Resources/${path}`).then(audio =>
setFile({
audioFile: new Audio(audio),
})
if (file.length !== 0) {
setBool(true)
}
);
}
useEffect(() => {
loadAudioFromPath(resourceURI)
console.log(file)
}, [bool])
useEffect(() => {
loadAudioFromPath(resourceURI)
console.log(file)
}, [])
There must be some other issue in the code as:
useEffect(() => { loadAudioFromPath(resourceURI) console.log(file) }, [])
will only render once when the component loads. Happy to look into the rest of the code, if you add some more texture to the problem.
inspired by dr.Telma i fixed it by putting the result of the promise in a separate state then using that to create a new Audio and set it in the other state
const [file, setFile] = useState({audioFile: {} })
const [comp, setComp] = useState({audioComp: {} })
useEffect(() => {
import(`../components/Media/Resources/${resourceURI}`).then(audio =>
setComp({
audioComp: audio.default,
})
).then(()=>
setFile({
audioFile: new Audio(comp.audioComp),
})
)
}, [])
This is happeing because you are watching for file changes, and you are making changes in file using setFile inside of useEffect, so you made the loop.
So to help you more, i need to know what you really is trying to achivie with that file.
1 - If you trying to just load the file once it is uploaded you can try something like:
const { file, setFile } = useState();
const { resourceURI, setResourceURI } = useState();
useEffect(() => {
loadAudioFromPath(resourceURI)
}, [resourceURI])
const loadAudioFromPath = (path) => {
import(`../components/Media/Resources/${path}`).then(audio =>
setFile({
audioFile: new Audio(audio),
})
);
}
2 - If you trying to load the file just once (when the page is mounted)(Will do nothing if you change the file):
const { file, setFile } = useState();
useEffect(() => {
loadAudioFromPath(resourceURI);
}, [])
const loadAudioFromPath = (path) => {
import(`../components/Media/Resources/${path}`).then(audio =>
setFile({
audioFile: new Audio(audio),
})
);
}
3 - Or if you want to load the file if the current file is diferent than last one you can do something like i did in that answer -> A property is undefined - switching from class components to function hooks components in React

React useEffect gets data from database but not in time to be used in the component

I am using useEffect to get data from an api.
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`/api/posts/getCats`
);
const cats = await response.json();
console.log(cats);
} catch (e) {
console.error(e);
}
};
fetchData();
}, []);
The problem is when I try to use it in the return, its value is undefined.
{cats.map((data) => {
cats has value when I console.log it.
I cannot use componentDidMount because all my code is functional components.
Edit: I updated the code as per answers below but still get
TypeError: cats.map is not a function
All answers below actually make sense but I am not sure why its not working.
export default function Posts() {
const [cats, setCats] = useState();
useEffect(() => {
fetch(`/api/posts/getCats`)
.then(res => res.json())
.then(setCats)
.catch(console.error);
}, []);
return (
<div>
{cats?.map((data) => {
<h4>{data.main}</h4>
})}
</div>
)
}
This is because React renders your screen before finishing to get response from API. When you render screen, variable cats doesn't have values. You can run useEffect after each rendering. You can rerender by changing state from useEffect (This technique is often used). Do not forget to add [] or [cats] as a dependency of useEffect (second params) otherwise you will get infinite loop.
Below code works even when cats === [] or some array.
export default () => {
const [cats, setCats] = useState([])
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`/api/posts/getCats`
);
const result = await response.json();
setCats(result)
} catch (e) {
}
};
fetchData();
}, []);
return (
<div>
{cats.map(cat => <div>cat</div>)}
</div>)
}
You have to map the cats data into state.
const [cats, setCats] = useState([]);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`/api/posts/getCats`
);
const data = await response.json();
setCats(data);
} catch (e) {
console.error(e);
}
};
fetchData();
}, []);
You need to
call setCats when the response comes back (right now, you're just logging it)
.map only once cats has been populated:
const [cats, setCats] = useState();
useEffect(() => {
fetch(`/api/posts/getCats`)
.then(res => res.json())
.then(result => setCats(result.cats))
.catch(console.error);
}, []);
return (
<div>
{cats?.map((data) => {
// ...

How do I fetch data and use it in useEffect()?

I'm trying to fetch data from an API and set a state with the data, but when I use the data in a child component, I get an [Unhandled promise rejection: TypeError: null is not an object (evaluating 'data.name')] warning.
Here is a gist of what I'm trying to do. Does anyone know why this might be occurring? I assume it's because the data isn't received from the API. I have tried adding an "isLoading" state and only returning the ChildComponent if it's false, but I still get the same warning (this might be because setNewProp in useEffect isn't updating when it receives the data from the API).
const ParentComponent = (props) => {
const [data, setData] = useState(null);
const [newProp, setNewProp] = useState();
const fetchData = async () => {
new DataService.retrieveData().then((response) => {
setData(response);
}
}
useEffect(() => {
fetchData();
setNewProp({ data, ...props });
}, []);
return (
<ChildComponent newProp={newProp} />
);
}
You cannot use an async function inside an useEffect lifecycle event. As a good solution i would recommend to fully utilize the useEffect hook and use it as an effect to the updated data.
const ParentComponent = (props) => {
const [data, setData] = useState(null);
const [newProp, setNewProp] = useState();
const fetchData = async () => {
new DataService.retrieveData().then((response) => {
setData(response);
}
}
useEffect(() => {
fetchData();
}, []);
useEffect(() => {
setNewProp({ data, ...props });
}, [data]);
return (
<ChildComponent newProp={newProp} />
);
}
I also want to point out that useEffect runs AFTER the first render. That means your ChildComponent will always receive "undefined" as first props, since there is no initial value set at:
const [newProps, setNewProp] = useState(); // initial value comes here to prevent errors
Looks like maybe you have missed the await that is needed in useEffect() to make the code wait until that fetch has finished:
Before:
useEffect(() => {
fetchData();
setNewProp({ data, ...props });
}, []);
After:
useEffect(() => {
(async () => {
await fetchData();
setNewProp({ data, ...props });
})();
}, []);
Note that useEffect() doesn't support async functions (because it needs the return value to be a cleanup function, or undefined. For example, see this article.
BUT even better might be something like:
const [data, setData] = useState(null);
const fetchData = async () => {
new DataService.retrieveData().then((response) => {
setData(response);
}
}
fetchData();
if (data) {
const newProp = { data, ...props };
}
In your code, you first call fetchData function, which calls a useState hook when it's done. Since useState hook works asynchronously, the state data will not be changed right after.
useEffect(() => {
fetchData(); // Called setData()
setNewProp({ data, ...props }); // At this point, data hasn't changed yet.
}, []);
So you can use useEffect hook again to watch for changes in your data state. Then you should set the new state of your newProp.
useEffect(() => {
(async () => {
await fetchData();
})();
}, []);
useEffect(() => {
setNewProp({...props, data });
}, [data]);

Resources