React useEffect causing infinite re-render despite passing argument to dependency array - reactjs

The 'myPosts' has an object with multiple posts inside it.. I wanted the user profile to immediately show the post after it is uploaded so I passed 'myposts' in the dependency array.
But the problem is that the component is re-rendering infinitely. How can I make it so that it re-renders once, only when a new post is uploaded? I can't understand why passing 'myposts' in the array is causing infinite renders instead of only once.
const [myposts, setPosts] = useState([]);
useEffect(() => {
fetch('/mypost', {
headers: {
cookie: 'access_key',
},
})
.then((res) => res.json())
.then((data) => {
// console.log(data);
setPosts(data.myposts);
});
}, [myposts]);

When fetch resolves, it modifies myposts, which triggers a fetch because it is listed as dependency of useEffect, which modifies myposts, and so it continues...
It seems that myposts depends on the result of the fetch, not the other way around. So I would suggest removing myposts from the dependency list.

The useEffect hook is called when myposts gets updated. In the final .then of your fetch, you're updating it via setPosts. The best way to fix this is by making the dependency array an empty array.
But this won't solve the issue of updating posts from the server, but this can also be done in a periodic function with setInterval. This would result in something like the code below.
const [myposts, setPosts] = useState([]);
const update = fetch('/mypost', {
headers: {
cookie: 'access_key',
},
})
.then((res) => res.json())
.then((data) => {
// console.log(data);
setPosts(data.myposts);
});
useEffect(() => {
update()
const interval = setInterval(update, 30000)
return () => clearInterval(interval)
}, []);

Related

It is ok to use useRef(), localStorage and useState() in order to keep state data after refreshing the page?

What I want to achieve, is to keep changes in the state between refresh.
Now I think about this solution below, (using localStorage with useRef()) but I'm suspicious about it, it seems like it isn't technically correct, what do you think about that? It is useRef() supposed to be used for cases like this one, or maybe there are other more convenient solutions? It is supposed to not use any database.
Is a little project, a movie app, not a prod or stuff like that, the 5mb from localStorage are pretty much enough.
State (fetched from the API)
const [popularMovies, setPopularMovies] = useState(false);
Fetch Data for state
function getPopularMoviesData() {
const url =
"https://api.themoviedb.org/3/movie/popular?api_key=60186105dc57d2473a4b079bdee2fa31&language=en-US&page=1";
fetch(url)
.then((response) => response.json())
.then((data) => {
setPopularMovies(data);
})
.catch((err) => console.error(err));
}
useEffect(() => {
getPopularMoviesData();
}, []);
useRef()
const prevPopularMovies = useRef();
keep our previous data after each re-render
useEffect(() => {
prevPopularMovies.current = popularMovies;
setPopularMovies(prevPopularMovies.current);
});
localStorage for keeping data on refresh
useEffect(() => {
const popularMoviesData = localStorage.getItem("popularMovies");
if (popularMoviesData !== null) {
setPopularMovies(JSON.parse(popularMoviesData));
}
}, []);
useEffect(() => {
localStorage.setItem("popularMovies", JSON.stringify(popularMovies));
}, [popularMovies]);
If the question is really just about persisting state through page reloads then all you really need is a state initializer function to initialize the state from localStorage, and the useEffect hook to save state updates to localStorage.
The useState hook will keep the popularMovies state value from render cycle to render cycle. There's nothing to worry about here as this is the default React state behavior, the state lives as long as the component is mounted.
Example:
const initializeState = () => {
return JSON.parse(localStorage.getItem("popularMovies")) || {};
};
...
const [popularMovies, setPopularMovies] = useState(initializeState);
useEffect(() => {
if (!popularMovies?.results?.length) {
getPopularMoviesData();
}
localStorage.setItem("popularMovies", JSON.stringify(popularMovies));
}, [popularMovies]);
function getPopularMoviesData() {
const url =
"https://api.themoviedb.org/3/movie/popular?api_key=60186105dc57d2473a4b079bdee2fa31&language=en-US&page=1";
fetch(url)
.then((response) => response.json())
.then((data) => {
setPopularMovies(data);
})
.catch((err) => console.error(err));
}
You can certainly do this as a way to cache state between reloads but ultimately this data can get lost if local storage is cleared on exit or for other reasons and so it is never a guarantee. One robust solution that attempts to solve this is immortal db. This package will sync state between localstorage, cookies and indexdb in attempt to have the data persist.

ReactJS array from usestate is still empty after setTimeout

please I'm solving one problem (just learning purposes). I'm using useState() hook and then, after some timeout I want add next items into array from remote fetch.
My code snippet look like:
const [tasks, setTasks] = useState([]);
const url = 'https://pokeapi.co/api/v2/pokemon?offset=0&limit=5';
// asnynchronous call. Yes, it's possible to use axios as well
const fetchData = async () => {
try {
let tasksArray = [];
await fetch(url)
.then((response) => response.json())
.then((data) => {
data.results.map((task, index) => {
// first letter uppercase
const taskName = task.name.charAt(0).toUpperCase() + task.name.slice(1);
tasksArray.push({ id: index, name: taskName });
});
});
console.log('Added tasks:' + tasks.length);
setTasks(_.isEmpty(tasks) ? tasksArray : [...tasks, tasksArray]);
} catch (error) {
console.log('error', error);
}
};
useEffect(() => {
// Add additional example tasks from api after 5 seconds with
// fetch fetchData promise
setTimeout(fetchData, 5000);
}, []);
Code works fine with useEffect() hook. But in async function my array is empty when I add some tasks within five seconds and it will be replaced by fetched data and one empty
I added Butter and Milk within 5 seconds to my Shopping list
But after timeout my tasks array will be replaced by remote fetched data.
And as you can see, tasks array lenght is 0 (like console.log() says)
Please, can you exmplain me, why my tasks array is empty if there exists 2 items before 5 seconds.
Of course, I'm adding my tasks to the list normally after hit Enter and handleSubmit
const handleSubmit = (e) => {
e.preventDefault();
//creating new task
setTasks([
{
id: [...tasks].length === 0 ? 0 : Math.max(...tasks.map((task) => task.id)) + 1,
name: newTask,
isFinished: false,
},
...tasks,
]);
setNewTask('');
}
Thanks for help or explain. It the problem that useEffect is called after rendering? Of this causing async behavior?
I could not understand your code fully correctly but my guess is
the fetchData function you have declared might refer to the tasks at the time of declaration.
so every time you call fetchData you might not see the changed tasks state...
if this is the case try using useCallback with the dependency tasks...
what useCallback does is it stores the function in memory and if smth in dependency changes the function's logic changes to dependencies you declared.
If you have used eslint, calling a function inside useEffect will give you error similar to below
The ‘fetchOrg’ function makes the dependencies of useEffect Hook (at line 6) change on every render. Move it inside the useEffect callback. Alternatively, wrap the ‘fetchOrg’ definition into its own useCallback() Hook
Your code is confusing. You can place everything inside an useEffect, and I believe the thing you are trying to achieve is a long poll query. (for that you use setInterval() and you have to clear the function in useEffect
my solution for you:
const [tasks, setTasks] = useState([]);
const url = 'https://pokeapi.co/api/v2/pokemon?offset=0&limit=5';
const request = {method: GET, Headers: {"Content-type":"application/json"}}
useEffect(() => {
function fetchData(){
fetch(url, request).then(res => res.json().then(data => {
data.results.map((task, index) => {
const taskName = task.name.charAt(0).toUpperCase() + task.name.slice(1);
setTasks((prevState) => ([...prevState,{ id: index, name: taskName }]));
});
})
}
const interval = setInterval(() => {
fetchData();
}, 5000)
return () => clearInterval(interval)
}, []);
please do not forget two things:
This approach is only good when you have 5 to 10 simultaneous clients connected since it is not performance effective on the backend, I would recommend instead an alternative based on realtime communication (with the listening for new events.
please do not forget to specify the {method, headers, body) in the fetch function

Setting state inside a promise inside a useEffect hook in React

I've been trying to figure out why my code doesn't work for an hour now. So basically I want to fetch some data from a MySQL database, my serverside code is working as expected but whenever I try to fetch it in the client with the following code setting the state fails:
const [data, setData] = useState(null);
useEffect(() => {
const loadData = () => {
fetch("http://localhost:5000/getusers")
.then((response) => response.json())
.then((data) => {
setData(data); // data is undefined but when consoled-out it's in proper form
});
};
loadData();
console.log(data);
}, [data]);
data is an array of objects. I assume I can't pass setState in a promise because I've added a conditional for rendering the data so even if it's null it just won't render but I receive a TypeError: data.map is not a function (it would be great if someone could explain how this happens).

After Pushing a Array Element in react.js using useState and useEffect it's looping infinite after update the state. How to solve?

I have push some a image elements in object array it's working fine but how to update the state?
const url = "https://jsonplaceholder.typicode.com/posts";
const [posts, setPosts] = useState([]);
const postsMap = posts.map(post => {
return {...post, "image": `image-${post.id}.jpg`}
})
console.log("Added Post Image", postsMap);
useEffect(()=>{
fetch(url)
.then(res => res.json())
.then(data => {
setPosts(data)
console.log(data);
})
}, [postsMap]);
I would say its because -
setPosts(data) in useEffect() updates post
update in post updates postsMap
update in postsMap triggers a re-render of the component
repeat the cycle.
I think the infinite loop is probably because of the wrong parameter passed to the dependency array provided to useEffect as the second parameter. I think passing an empty array as a parameter to useEffect's second argument should solve the problem.
Because your useEffect is dependend on a variable that changes within it self (setPosts). So you create an infinite Loop. You don’t need postmap as dependency, use url as dependency.

React: how can I process the data I get from an API before I render it out?

I have no issues fetching the data from an API using useEffect. That works fine.
The problem is that I need to apply some processing to the data before I actually render it out (in this case, I need to shuffle the array that I receive).
I tried a million different ways, but I just can't find the right place to write that logic. Basically, it won't work anywhere.
What is the right way of going about this?
you can do everything with data before setState.
is useEffect when you fetched data from Api, shuffle it and then do setState.
little example:
useEffect(() => {
axios.get("http://example.com/data").then(response => {
const data = shuffle(response.data);
setState(data);
})
});
useEffect(() => {
const fetchData = async () => {
await axios.get("http://example.com/data").then(response => {
const data = shuffle(response.data);
setState(data);
});
};
fetchData();
return () => {
// Clean up func
}
}, []); //[] will prevent infinite API calling.

Resources