Why is `useEffect` not re-running when the dependencies change? - reactjs

Background:
I was trying to fetch all the links for a set of images, then fetch the real assets for images using the links fetched.
let imageLinks = [];
useEffect(() => {
fetch("http://localhost:3001/video/1?offset=0&count=100")
.then((res) => res.json())
.then((res) => {
imageLinks = res.frames;
});
}, []);
useEffect(() => {
Promise.all(
imageLinks.map((link) =>
fetch("http://localhost:3001/" + link).then((res) => res.json())
)
).then((res) => console.log(res));
// update when the image links changed
}, [imageLinks]);
The last useEffect didn't seem to be working, even when imageLinks has been updated in the previous useEffect. Could anyone please tell me why is that?

In your example, imageLinks is just a variable inside the React component. If the component's prop or state was to change, the variable's value would be reset to [] on each re-render.
To keep React in sync with imageLinks current value, you would need to save it in state. To do this, you need to use useState.
In the example below, when setImageLinks is called it will get stored in React's state, the component will re-render and it will be passed to the effect. The effect will then check if imageLinks has changed and run the effect, if so:
const [imageLinks, setImageLinks] = React.useState([]);
useEffect(() => {
fetch("http://localhost:3001/video/1?offset=0&count=100")
.then((res) => res.json())
.then((res) => {
setImageLinks(res.frames);
});
}, []);
useEffect(() => {
Promise.all(
imageLinks.map((link) =>
fetch("http://localhost:3001/" + link).then((res) => res.json())
)
).then((res) => console.log(res));
// update when the image links changed
}, [imageLinks]);
Further to this, it may simplify your code if you group your fetches together rather than having them as separate effects.

Related

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.

how to use a hook and avoid a loop

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();
}, [])

How to stop useEffect from reloading my page every time?

For some reason my whole page reloads every time it updates the state after it gets it from the database. The page flickers and I end up at the top of the page. Why is this?
I update the entire state in other functions like sort(), that works perfect without reloading. I have put event.preventDefault() in every click handler so that shouldn't be the problem.
One of the great things with using React is to have a smooth UI without reloading so this is annoying.
function App() {
const [contacts, setContacts] = useState({ items: [] });
useEffect(() => {
axios
.get('http://localhost:5000/')
.then((result) => {
setContacts({ items: result.data });
})
.catch((err) => console.log(err));
}, []);
And this is the function that gets called:
const handleSubmit = (event) => {
event.preventDefault();
if (!id) {
axios
.post('http://localhost:5000/add/', input)
.then(() => {
setInput(emptyState);
})
.catch((err) => console.log(err));
} else {
axios
.post(`http://localhost:5000/update/${id}`, input)
.then(() => {
props.updateContact(input);
setInput(emptyState);
})
.catch((err) => console.log(err));
}
window.location = '/';
};
You need to put something in your [].
You can see that we passed props.name into the array in the second argument. This will now cause the effect to always run again when the name changes.
If you don't pass anything it will always update and will be useless.
useEffect(() => {
document.title = `Page of ${props.name}`
}, [props.name])

value of state is always default. React js 16.12.0

I have two useEffect-s. One is used to fetch data from api and save it in the state and second is called only once and it starts listening to websocket event.
In the websocket event handler I log the fetched data but it always has the default value.
Even though fetching data completes successfully and the list is drawn on UI, the value of list is always empty - [].
const [list, setList] = useState([]);
useEffect(() => {
axios.get("https://sample.api.com/get/list")
.then(res => {
setList(res.data);
});
}, [window.location.pathname.split('/')[2]]);
useEffect(() => {
webSocket.on('messageRecieved', (message) => {
console.log(list);
});
}, []);
Your second effect is referencing the initial list value (an empty array) due to closure. This is why useEffect should reference all of its dependencies in its second argument.
But in this case, where you don't want to subscribe to the webSocket event each time the list is updated, you could use React's refs on the list.
const listValue = useRef([]);
const [list, setList] = useState(listValue.current);
When setting the value:
res => {
listValue.current = res.data
setList(listValue.current);
}
And when retrieving the list in a one time fired useEffect:
useEffect(() => {
webSocket.on('messageRecieved', (message) => {
console.log(listValue.current);
});
}, []);
try changing
.then(res => {
to
.then((res) => {
Would clarify if you added console logs to each hook or said if the values are preset in them:
useEffect(() => {
axios.get("https://sample.api.com/get/list")
.then((res) => {
console.log(res.data)
setList(res.data);
});
}, [window.location.pathname.split('/')[2]]);
useEffect(() => {
webSocket.on('messageRecieved', (message) => {
console.log(list);
console.log(message);
});
}, []);
You could also add error catch, just in case:
.catch((error) => {
console.log(error.response)
})

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