how to use a hook and avoid a loop - reactjs

I want the fetchTasks() function to be called when I start the component, I know that componentDidMount() is used with classes, but in this way useEffect is used, when I use it, I enter a loop, although it does not send me warnings or errors it sends constant requests to API.
const [tasks, setTasks] = useState([]);
const fetchTasks = (e) => {
fetch('/api/tasks')
.then(res => res.json())
.then(data => {
setTasks(data)
console.log(tasks);
})
.catch(err => console.error(err))
}
useEffect(() => {
fetchTasks();
})
I only want to get the API data once when rendering the component.
Would it be correct to take advantage of this loop to use it as a socket with the API?
Thank you very much, I haven't been in React long.

Use square brackets in useEffect like
useEffect(() => {
fetchTasks();
},[])
If you want to call this again on change of anystate then call like
useEffectt(() => {
fetchTasks();
},[state_variable_name])

To only fetch your data onces inside useEffect you have to provide an empty dependancy array.
useEffect(() => {
fetchTasks();
}, [])

Related

Refactoring useEffect to only require one database call

At the moment, I have a component which completes some backend calls to decide when to start displaying the UI.
It's structured like this:
useEffect(() => {
getData()
})
const getData = async () => {
await verifyUser()
await fetchData()
}
The purpose here, is that verifyUser() is supposed to run first, and in the response to verifyUser(), a user id is provided by the backend.
const verifyUser = async () => {
if (!localStorage.getItem('auth')) {
return
}
if (localStorage.getItem('auth')) {
await axios.post("/api/checkAuth", {
token: JSON.parse(localStorage.getItem('auth'))
})
.then((response) => {
return setUserId(response.data.user_id)
})
.catch((err) => {
console.log(err)
localStorage.removeItem('auth')
})
}
}
As a result of this, the fetchData() function is supposed to wait until the verifyUser() function has stopped resolving, so it can use the user id in the database query.
However, at the moment it...
Calls once, without the user id
Then calls again, with the user id (and therefore resolves successfully)
Here's the function for reference:
const fetchData = async () => {
console.log("Fetch data called.")
console.log(userId)
await axios.post("/api/fetch/fetchDetails", {
user_id: userId
})
.then((response) => {
// Sets user details in here...
return response
})
.then(() => {
return setFetching(false)
})
.catch((err) => {
console.log(err)
})
}
What I'm trying to achieve here is to essentially remove any concurrency and just run the functions sequentially. I'm not 100% sure what the best practice here would be, so some feedback would be appreciated!
Your useEffect is missing a dependency array argument:
useEffect(() => {
getData()
})
should be:
useEffect(() => {
getData()
}, [])
Without that argument, useEffect will run once each time your component renders. With that argument, it will only run once, when the component is first mounted (ie. after the first render).
If you needed it to depend on another variable (eg. user.id isn't defined on load, but is later on) you could put that variable in the dependency array, ie.
useEffect(() => {
if (!user.id) return;
getData()
}, [user.id])
This version would run once when the component is mounted, then again if the user.id changes (eg. if it goes from null to an actual number).
In React, the useEffect hook accepts two arguments - the first one is a function (this is the "effect"), and the second one is a dependency array. The simplest useEffect hook looks like this:
useEffect(() => {
}, [])
The above hook has no dependency (because the array is empty), and runs only when the component initially mounts, and then goes silent.
If you don't pass in a dependency array as the second argument, as #machineghost said, the hook will run the "effect" function every time your component re-renders.
Now to your specific problem. You want to run fetchData after verifyUser has resolved its Promise, so you'd add the outcome of verifyUser as a dependency to a separate useEffect hook that calls fetchData. In this case, the outcome is setting userId.
So instead of this:
useEffect(() => {
getData()
})
const getData = async () => {
await verifyUser()
await fetchData()
}
Do this:
useEffect(() => {
verifyUser();
}, []);
useEffect(() => {
if (userId) { // assuming userId has a false-y value before verifyUser resolved
await fetchData();
}
}, [userId])

Is there any benefit to using more than one useEffect?

I recently stumbled over some code where two useEffect hooks were used as opposed to one. Is there any difference between using one big function and multiple? The following code does the job, so I guess I'm just curious.
const MyProjects = () => {
const [data, setData] = useState([])
const [dataLoaded, setDataLoaded] = useState(false)
let clone = useRef(null);
async function fetchData() {
await fetch('https://jsonplaceholder.typicode.com/todos')
.then(response => response.json())
.then(json => {
setData(json)
clone.current = [...json]
setDataLoaded(true)
})
// .then(setData(data.map((item) => ({...item, backgroundColor: true}))))
.catch((err) => {
console.log(err);
});
}
useEffect(() => {
fetchData()
}, [])
useEffect(() => {
if (!dataLoaded) return
const newArray = clone.current.map(item => (
{...item, backgroundColor: 'orange'}
))
// console.log(clone.current)
setData([...newArray])
console.log(data)
}, [dataLoaded]);
Like all things in programming, the answer is still the same, it honestly depends on the use case.
Case 1: Separating for functionality
The code you have given demonstrates this use case. In your code, the first useEffect is used to fetch the data. This has an empty dependency list and so it will only run once on mounting ( First Render ).
useEffect(() => {
fetchData()
}, [])
While the second useEffect will run but return on the First Render, and only run again each time the dataLoaded variable is mutated
useEffect(() => {
if (!dataLoaded) return
const newArray = clone.current.map(item => (
{...item, backgroundColor: 'orange'}
))
// console.log(clone.current)
setData([...newArray])
console.log(data)
}, [dataLoaded]);
Now it makes sense to keep both functionalities separate because if you kept them together. Then on each data modification, fetchData would've run again. So yes there is a functional benefit in separating useEffects depending on which code you want to run on dependency changes.
Case 2: Separating for code readability and debugging
Usually if you have a useEffect that has too many lines of code. It is better to separate them into different useEffects grouping same purpose code into different useEffects. This will help in code readability and easier debugging afterwards.

Multiple API Calls with Promise.all

I'm trying to get different data from different APIs. I don't have any problem when getting one and updating state. But I can't figure out how can I update two different state with Promise.all()
How can I make this code work.
const [stats, setStats] = useState(null);
const [info, setInfo] = useState(null);
React.useEffect(()=>{
Promise.all([
fetch('https://api.opensea.io/api/v1/collection/nickelodeon-rugrats-heyarnold-eth/stats'),
fetch('https://api.opensea.io/api/v1/asset_contract/0x223E16c52436CAb2cA9FE37087C79986a288FFFA')])
.then(res =>Promise.all(res.map(r=> r.json())))
.then((stats) => {
setStats(stats);
})
.then((info) => {
setInfo(info);
})
.then(data => console.log(data)).catch(error => console.log(error));
},[])
The Promise.alls are resolving to an array of resolve values, so the later .thens should use that array - the two URLs being fetched doesn't result in two separate .thens, it only results in a single Promise that you need to extract two properties out of.
You can also make the code simpler and DRYer by starting with an array of the two URLs and mapping over them.
React.useEffect(() => {
const urls = ['https://api.opensea.io/api/v1/collection/nickelodeon-rugrats-heyarnold-eth/stats', 'https://api.opensea.io/api/v1/asset_contract/0x223E16c52436CAb2cA9FE37087C79986a288FFFA'];
Promise.all(urls.map(url => fetch(url).then(r => r.json())))
.then(([stats, info]) => {
setStats(stats);
setInfo(info);
})
.catch(error => console.log(error));
}, []);

Troubles with using hook useState()

Trying to use fetched data into hook useState(fetchedData)
const [car, setCar] = useState({images: []});
useEffect( () => {
fetchOneCar(id)
.then(data => setCar(data))
.finally(() => setLoading(false))
},[id]);
const [images,setImages] = useState(car.images)
console.log(images) // -> [] unpredictably empty
console.log(car.images) // -> [{},{},{}] (fetched data)
How to properly set data into useState() in my case ?
ok look first car is {images:[]}
then images is []
and then car turns into whatever data you fetched in use effect
just because you declare useState after use effect doesn't mean it will run after useEffect.
First all the useStates run and then the effects. that's the law.
so there is no unexpected result.
To fix this in yur use effect do this:
useEffect( () => {
fetchOneCar(id)
.then(data => {
setCar(data);
setImages(data)
})
.finally(() => setLoading(false))
},[id]);
According to your code, I expect that you want to fill the images with the result from data. If it is, then you have to put the setImages(data.images) inside the resolved promise, after the setCar(data).
It should be like this one
const [car, setCar] = useState({images: []});
const [images,setImages] = useState();
useEffect( () => {
fetchOneCar(id)
.then(data => {
setCar(data);
setImages(data.images);
})
.finally(() => setLoading(false))
},[id]);
I put the useState() for images at the top for better reading.

React state hook doesn't properly handle async data

I'm trying to set a component's state through an effect hook that handles the backend API. Since this is just a mock, I'd like to use the vanilla react methods and not something like redux-saga.
The problem is that while the fetching part works, the useState hook doesn't update the state.
const [odds, setOdds] = useState({})
useEffect(() => {
(async () => {
fetchMock.once('odds', mocks.odds)
let data = await fetch('odds').then(response => response.json())
setOdds(data)
console.log(odds, data) // {}, {...actual data}
})()
}, [])
I've tried to pipe the whole process on top of the fetch like
fetch('odds')
.then(res => res.json())
.then(data => setOdds(data))
.then(() => console.log(odds)) // is still {}
But it doesn't make a single difference.
What am I doing wrong?
Basically if you call setOdds, the value of odds does not change immediately. It is still the last reference available at decleration of the hook.
If you want to access the new value of odds after updating it, you would have to either use the source of the updated value (data) if you want to access the value in the same useEffect hook or create another useEffect hook that triggers only when odds has changed:
useEffect(() => {
console.log(odds);
// Do much more
}, [odds]) // <- Tells the hook to run when the variable `odds` has changed.
If you want to see that state has changed in here, you can use
const [odds, setOdds] = useState({})
useEffect(() => {
(async () => {
fetchMock.once('odds', mocks.odds)
let data = await fetch('odds').then(response => response.json())
setOdds(prevData => {
console.log(prevData, data) // {}, {...actual data}
return data
})
})()
}, [])

Resources