Fetch method in useEffect is in infinite loop - javascript

I was getting the infinite loop of the code getting executed, but I couldn't find where the problem was. As I am new with react I sometimes struggle to see the problem. Could it be the dependecy or that I forgot to close something off?
import {useCookies} from "react-cookie";
import React, {useEffect, useState} from "react";
import CircularProgress from "#mui/material/CircularProgress";
import {Navigate} from "react-router-dom";
const Date = () => {
const [cookies] = useCookies(['XRSF-TOKEN']);
const [loading, setloading] = useState(false);
const [error, seterror] = useState(false);
const [users, setusers] = useState([]);
useEffect(() => {
setloading(true);
fetch("/allotherusers", {credentials: "include"})
.then(response => response.text())
.then(body => {
if (body === '') {
return <Navigate to="/" />
}
else {
setusers(JSON.parse(body));
}
setloading(false);
});
}, [setusers, setloading])
const userlist = users.map(user => {
return (
<div>
<p> {user.firstname}</p>
<button onClick={() => like(user, false)}>Like</button>
</div>
)
});
const like = async (likeduser, superlike) => {
const likemodel =
{
likeduser: likeduser,
superlike: superlike
}
await fetch('/liked', {
method: 'POST',
headers: {
'X-XSRF-TOKEN': cookies['XRSF-TOKEN'],
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(likemodel),
credentials: 'include'
});
};
return (
<div>
{userlist}
</div>
);
}
export default Date;
Here you see how the frontend is keep calling the api the request.

Because both setusers, setloading is updating inside the useEffect which causes to fire the useEffect again. Just remove the side Effects from the useEffect array
}, [])

inside of your effect function, when you do setusers(JSON.parse(body)) and setloading(false) you are updating userData to a new array. Even if all the items inside of that new array are exactly the same, the reference to the new userData array has changed, causing the dependencies of the effect to differ, triggering the function again -- ad infinitum.
One simple solution is to simply remove setusers from the dependency array. In this way, the useEffect function basically acts similar to componentDidMount in that it will trigger once and only once when the component first renders.
useEffect(() => {
setloading(true);
fetch("/allotherusers", {credentials: "include"})
.then(response => response.text())
.then(body => {
if (body === '') {
return <Navigate to="/" />
}
else {
setusers(JSON.parse(body));
}
setloading(false);
});
}, [])
I think you should try this.!

yes the problem is dependency,you are using callback(setState) as dependency ,you can keep and empty dependency since on the page render the api gets called and your state gets set.
useEffect(() => {
function fetchUsers(){
setloading(true);
fetch("/allotherusers", {credentials: "include"})
.then(response => response.text())
.then(body => {
if (body === '') {
return <Navigate to="/" />
}
else {
setusers(JSON.parse(body));
}
setloading(false);
});
}
fetchUers();
}, [])

Related

Search function now working in React photo gallary

Working on a small application that takes a pexels api and displays photos dynamically. When I send the search request for my api to fectch based on the new params, it does actually update the page with new photos but not the ones based on the params. I though I got the search function correct, maybe it's cause I'm not using it in a useEffect? But if I did use it in a useEffect, I wouldn't be able to set it on the onClick handle. I tried to console.log the query I was getting from the onChange but it doesn't seem like it's getting the result. What am I doing wrong?
import { useState, useEffect } from 'react'
import pexelsApi from './components/pexelsApi'
import './App.css'
const App = () => {
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(false);
const [nextPage, setNextPage] = useState(1);
const [perPage, setPerPage] = useState(25);
const [query, setQuery] = useState('');
const [error, setError] = useState('');
useEffect(() => {
const getImages = async () => {
setLoading(true);
await pexelsApi.get(`/v1/curated?page=${nextPage}&per_page=${perPage}`)
.then(res => {
setImages([...images, ...res.data.photos]);
setLoading(false);
}).catch(er => {
if (er.response) {
const error = er.response.status === 404 ? 'Page not found' : 'Something wrong has happened';
setError(error);
setLoading(false);
console.log(error);
}
});
}
getImages();
}, [nextPage, perPage]);
const handleLoadMoreClick = () => setNextPage(nextPage + 1)
const search = async (query) => {
setLoading(true);
await pexelsApi.get(`/v1/search?query=${query}&per_page=${perPage}`)
.then(res => {
setImages([...res.data.photos]);
console.log(res.data)
setLoading(false);
console.log(query)
})
}
if (!images) {
return <div>Loading</div>
}
return (
<>
<div>
<input type='text' onChange={(event) => setQuery(event.target.value)} />
<button onClick={search}>Search</button>
</div>
<div className='image-grid'>
{images.map((image) => <img key={image.id} src={image.src.original} alt={image.alt} />)}
</div>
<div className='load'>
{nextPage && <button onClick={handleLoadMoreClick}>Load More Photos</button>}
</div>
</>
)
};
export default App
import axios from 'axios';
export default axios.create({
baseURL: `https://api.pexels.com`,
headers: {
Authorization: process.env.REACT_APP_API_KEY
}
});
Your main issue is that you've set query as an argument to your search function but never pass anything. You can just remove the arg to have it use the query state instead but you'll then need to handle pagination...
// Helper functions
const getCuratedImages = () =>
pexelsApi.get("/v1/curated", {
params: {
page: nextPage,
per_page: perPage
}
}).then(r => r.data.photos)
const getSearchImages = (page = nextPage) =>
pexelsApi.get("/v1/search", {
params: {
query,
page,
per_page: perPage
}
}).then(r => r.data.photos)
// initial render effect
useEffect(() => {
setLoading(true)
getCuratedImages().then(photos => {
setImages(photos)
setLoading(false)
})
}, [])
// search onClick handler
const search = async () => {
setNextPage(1)
setLoading(true)
setImages(await getSearchImages(1)) // directly load page 1
setLoading(false)
}
// handle pagination parameter changes
useEffect(() => {
// only action for subsequent pages
if (nextPage > 1) {
setLoading(true)
const promise = query
? getSearchImages()
: getCuratedImages()
promise.then(photos => {
setImages([...images, ...photos])
setLoading(false)
})
}
}, [ nextPage ])
The reason I'm passing in page = 1 in the search function is because the setNextPage(1) won't have completed for that first page load.

Multiple useEffect not working as expected

useEffect(() => {
debugger;
}, [filter]);
// eslint-disable-next-line
useEffect(async () => {
if (parseInt(localStorage.getItem("lastFetchTime")) + 8640000 > Date.now()) {
setRecipeList(JSON.parse(localStorage.getItem("recipeList")));
setIsLoading(false);
} else {
await fetch('https://api.spoonacular.com/recipes/random?number=20&apiKey=3c6b5aedfaf34bb899d1751ea2feb1b2')
.then((resp) => resp.json())
.then((data) => {
setRecipeList(data.recipes);
setIsLoading(false);
localStorage.setItem("recipeList", JSON.stringify(data.recipes));
localStorage.setItem("lastFetchTime", Date.now());
})
}
}, []);
I have these 2 useEffect in my program, the first one, with the listener is not being called even if the filter is changed. But it works if I remove the [] from the 2nd useEffect and the 2nd one runs on loop so I cant use it like that. I saw multiple forums, all of which suggests this should work.
import { useState, useEffect } from "react";
import { render } from "react-dom";
const sleep = (ms: number) => new Promise(
resolve => setTimeout(() => resolve('Resolved'), ms));
function App() {
const [filter, setFilter] = useState({ count: 0 });
const [get, set] = useState(0);
useEffect(() => {
console.log('Here');
}, [filter]);
useEffect(() => {
async function myFunction() {
const res = await sleep(5000)
.then(res => console.log(res));
setFilter({ ...filter, count: filter.count + 1 });
}
myFunction();
}, [get]);
return (
<div>
<p>App {get}</p>
<button onClick={() => set((get: number) => get + 1)}>
Click
</button>
</div>
);
}
render(<App />, document.getElementById("root"));
This minor snippet to be working for me as expected.
useEffect cannot be async. If you want to call an async function in useEffect() you need to do it like this:
EDIT: this is the complete useEffect
useEffect(() => {
async function getData() {
if (
parseInt(localStorage.getItem("lastFetchTime")) + 8640000 >
Date.now()
) {
setRecipeList(JSON.parse(localStorage.getItem("recipeList")));
setIsLoading(false);
} else {
const res = await fetch(
"https://api.spoonacular.com/recipes/random?number=20&apiKey=3c6b5aedfaf34bb899d1751ea2feb1b2"
);
const data = await res.json();
setRecipeList(data.recipes);
setIsLoading(false);
localStorage.setItem("recipeList", JSON.stringify(data.recipes));
localStorage.setItem("lastFetchTime", Date.now());
}
}
getData();
}, []);
I tested it and it worked as expected (I console.log() in the other useEffect())
There's nothing wrong with the useEffect. It's a bullet proof. But you make sure the following things:
Is filter updated during the component did mount?
The debugger will show up if you have open developer tool.
Isfilter updated during the component did update?
The debugger won't show up.
To make sure whenfilter is updated, use another effect hook but this time without dependency array.
useEffect(()=>{
console.log(filter) // analyze in the console
})
And if the value is updated during the update then you don't need to use dependency array but check the changes inside the effect hook by using some state for that as filter is coming from the update (props).
import { useState, useEffect, useCallback } from "react";
function App() {
const [isLoading, setIsLoading] = useState(false);
const [filter, setRecipeList] = useState({});
useEffect(() => {
// debugger;
}, [filter]);
// eslint-disable-next-line
const fetchData = useCallback(async () => {
if (
parseInt(localStorage.getItem("lastFetchTime")) + 8640000 >
Date.now()
) {
setRecipeList(JSON.parse(localStorage.getItem("recipeList")));
setIsLoading(false);
} else {
const data = await fetch(
"https://api.spoonacular.com/recipes/random?number=20&apiKey=3c6b5aedfaf34bb899d1751ea2feb1b2"
).then((resp) => resp.json());
setRecipeList(data.recipes);
setIsLoading(false);
localStorage.setItem("recipeList", JSON.stringify(data.recipes));
localStorage.setItem("lastFetchTime", Date.now());
}
}, []);
useEffect(() => {
setIsLoading(true);
fetchData();
}, [fetchData]);
return (
<div>
<span>{isLoading ? "loading" : "loaded!"}</span>
{!isLoading && filter && <div>filter size:{filter.length}</div>}
</div>
);
}
export default App;
I think it will work properly.
Thanks.

Need help to display data from external API

I fetched some data from an API, Im trying to display the data but I'm doing something wrong. Can someone help? I have attached a photos of the fetched data on the console and my code[![data api
import React, {useState, useEffect} from 'react'
import './Track.css';
export default function Track() {
const [carbon] = useState([])
useEffect( () => {
const headers = {
'Accept':'application/json'
};
fetch('https://api.carbonintensity.org.uk/intensity',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
})
return (
<div>
<p>Track</p>
<div>
<p>{carbon.forecast}</p>
</div>
</div>
)
}
]1]1
Change to
import React, { useState, useEffect } from 'react'
import './Track.css';
export default function Track() {
const [carbon, setCarbon] = useState([])
useEffect(() => {
const headers = {
'Accept': 'application/json'
};
fetch('https://api.carbonintensity.org.uk/intensity',
{
method: 'GET',
headers: headers
})
.then(function (res) {
setCarbon(res.data)
}).then(function (body) {
console.log(body);
});
})
return (
<div>
<div>
{carbon.map((obj, i) => (
<li key={i}>
<ul>{obj.from}</ul>
</li>
))}
</div>
</div>
)
}
I recommend to you study https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Array/map
you forgot some little thing:
first you forgot the setCarbon in the useStae hook you will need it to pass the response from the fetch.
You forgot to set the state in the fecth.
You will need to add a condition to render only when the state (carbon) is set.
you need to add an empty dependency to useEffect
import React, { useState, useEffect } from "react";
export default function Track() {
const [carbon, setCarbon] = useState([]);
useEffect(() => {
const headers = {
Accept: "application/json"
};
fetch("https://api.carbonintensity.org.uk/intensity", {
method: "GET",
headers: headers
})
.then((res) => {
return res.json();
})
.then((body) => {
console.log(body.data);
setCarbon(body.data);
});
}, []);
return (
<div>
<p>Track</p>
{carbon.length > 0 && (
<div>
{carbon.map((c, i) => (
<p key={i}>
<div>from: {c.from} </div>
<div>to: {c.to}</div>
<div>forecast: {c.intensity.forecast}</div>
<div>actual: {c.intensity.actual}</div>
<div>index: {c.intensity.index}</div>
</p>
))}
</div>
)}
</div>
);
}
Here you go,
Remember, state is like a place to store data for your component.
When you use fetch, you are getting data and now you need to save it to your state.
If you use state inside of your JSX, you can get the information to display.
Check out the console log, to look at the data structure that is returned from the fetch. This is what is set to the state "data". It can be called whatever you want. You can iterate through it, and dynamically display the data in JSX if you wanted, but I just hardcoded it for you so it's easier to understand.
const [data, setData] = useState([]);
useEffect(() => {
fetch("https://api.carbonintensity.org.uk/intensity", {
method: "GET",
headers: {
"Content-Type": "application/json"
}
})
.then((res) => res.json())
.then((data) => setData(data))
.catch((e) => console.error(e));
}, []);
console.log("data:", data);
return (
<div>
<p>Track</p>
<div>
<p>From: {data.data["0"].from}</p>
<p>To: {data.data["0"].to}</p>
<div>Intensity:</div>
<p>forecast: {data.data["0"].intensity.forecast}</p>
<p>forecast: {data.data["0"].intensity.actual}</p>
<p>forecast: {data.data["0"].intensity.index}</p>
</div>
</div>
);

Can I use two useEffect and have map inside a map

I am new to React and would like some help with the following problem. I current have this code.
import React, { useState, useEffect } from "react";
function FetchData() {
const [repos, setRepos] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetch("https://api.github.com/orgs/org_name/repos")
.then((res) => res.json())
.then((data) => {
setRepos(data);
})
.then(() => {
setIsLoading(false);
})
.catch((err) => console.log(err));
}, []);
return (
<div>
{repos.map((repo) => (
<div key={repo.id}>
<div>
<h2>Name: {repo.name}</h2>
<p>Top 5 Contributors</p>
))}
My above codes work fine, but my problem now is that I would like to add the top 5 contributors to the repository and to access that I have to go to https://api.github.com/repos/org_name/{repos}/contributors, and to get to that, I first have to use repo.contributor_url Should I use another useEffect and map to show the top 5 contributors?
Edit
Basically I want to do something like this.
useEffect(() => {
fetch(`${repos.contributors_url}`)
.then((res) => res.json())
.then((data) => {
setContributors(data);
console.log(data);
})
.catch((err) => console.log(err));
}, []);
...
<p> Top 5 Contributors: </p>
<ul>
{contributors.map((c, i) => {
<li key={i}>{c.name}</li>
)}
</ul>
Since you are new to React. React used to have class based components to handle state and those class based components had special functions called- Life-Cycle-Methods. But from React 16.8 onwards React Community came up with React-Hooks and functional components can now be used to handle state and useState() and useEffect() are examples of Hooks.
Now useEffect() alone is used to do perform life-cycle method's work.
The way you have used useEffect() in your code is simulating componentDidMount() as you have kept the 2nd argument as an empty array []
We can use other life-cycle methods like componentDidUpdate() and componetnWillUnmount() using useEffect() Hook itself.
Then based on your requirement you can use useEffect() Hook as many times as required by your Component.
Coming to Updated part of your question now:
So, you basically need to do promise chaining. We know that fetch() is promise based,so when one asynchronous call is resolved and we get the first data, within your useEffect() hook only, you need to make another asynchronous request using the second url-end point to get the respective data.
Here is the updated code now: Try this
import React, { useState, useEffect } from 'react';
function FetchData() {
const [repos, setRepos] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [contributors, setContributors] = useState([]);
const [isContributorLoading, setIsContributorLoading] = useState(true);
useEffect(() => {
fetch('https://api.github.com/orgs/{org}/repos')
.then((res) => res.json())
.then((data) => {
setRepos(data); // Data 1(repos) is received
// Now We make another API call to get Data 2 (contributors)
return fetch('https://api.github.com/repos/{org}/{repos}/contributors');
})
.then((res) => res.json()) // Chaining promise,handling 2nd Fetch request
.then((data2) => {
console.log(data2);
setContributors(data2);
})
.then(() => {
setIsLoading(false);
})
.catch((err) => console.log(err));
}, []);
return (
<div>
{ repos.length && repos.map((repo) => (
<div key={repo.id}>
<div>
<h2>Name: {repo.name}</h2>
</div>
</div>
))}
<p> Top 5 Contributors: </p>
<ul>
{contributors.length && contributors.map((c, i) => {
return <li key={i}>{c.name}</li>
)}
</ul>
</div>
);
}
So, basically you need to learn a bit more about how to use Hooks especially useEffect(), for now. Do some googling stuff, It would not be good if I tell you everything now. Give it a shot then.
You can directly call apis inside one useEffect.
import React, { useState, useEffect } from "react";
function App() {
const [repos, setRepos] = useState([]);
const [contributor, setContributor] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
async function caller() {
try {
setIsLoading(true);
const response = await fetch(
"https://api.github.com/orgs/octokit/repos"
);
const result = await response.json();
const contri = [];
console.log(result);
result.forEach((item) => {
contri.push(fetch(`${item.contributors_url}`));
});
Promise.all(contri)
.then((contributorResults) => contributorResults)
.then((responses) => {
console.log(responses);
return Promise.all(responses.map((r) => r.json()));
})
.then((cont) => {
setContributor([...cont])
});
setRepos(result);
} catch (err) {
console.log(err);
} finally {
setIsLoading(false);
}
}
caller();
}, []);
return (
<div>
{repos.map((repo,index) => (
<div key={repo.id}>
<h2> Name: {repo.name} </h2>
{ contributor[`${index}`] && contributor[`${index}`].slice(0,5).map(item => {
return <div key={item.id}>
<div>{item.login}</div>
</div>
})}
</div>
))}
{isLoading && <div>...loading</div>}
</div>
);
}
export default App;

React hooks array passing in number when passed into props of component

I am currently working on a chat application and for some reason every time I pass in my array of messages as a prop to another component it passes in a number to the component instead of the message object. I have tried a lot of different methods of passing it in regarding using multiple components etc but it seems to still be passing in the number of elements for some reason. Any help is appreciated... code is below
Component receiving the props
import React, { useEffect } from 'react'
import Message from '../../Message/Message'
function Messages({ messages }) {
useEffect(() => {
console.log(messages)
}, [messages])
return (
<div>
test
</div>
)
}
export default Messages
// Import React dependencies.
import React, { useEffect, useState, } from "react";
// Import React dependencies.
import io from 'socket.io-client'
import axios from 'axios'
import Messages from './Messages/Messages'
import uuid from 'react-uuid'
import { Redirect } from 'react-router-dom'
// Import the Slate components and React plugin.
const ENDPOINT = 'http://localhost:5000/'
export const socket = io.connect(ENDPOINT)
const LiveChatFunction = ({ group_id }) => {
// Add the initial value when setting up our state.
const [message, setValue] = useState("")
const [user, setUser] = useState("")
const [groupId, setGroup] = useState('')
const [messages, setMessages] = useState([])
const [toLogin, userAuth] = useState(false)
useEffect(() => {
setGroup(group_id)
axios.post('http://localhost:5000/api/users/refresh_token', null, { withCredentials: true }).then(data => {
if (!data.data.accessToken) {
userAuth(true)
}
})
axios.get('http://localhost:5000/api/users/userInfo', { withCredentials: true }).then(data => {
setUser(data.data.user)
})
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
axios.get(`http://localhost:5000/live/${group_id}`).then(x => {
console.log(x.data)
})
}, [group_id, messages])
function setClick() {
const data = {
messageId: uuid(),
user,
groupId,
message
}
socket.emit('message', data)
}
if (toLogin) {
return (
<Redirect to="/login" />
)
}
return (
<div>
<input placeholder="message" type="text" onChange={value => {
setValue(value.target.value)
socket.emit('typing-message', { username: user, time: new Date() })
}} />
<button onClick={setClick}>Submit</button>
<Messages messages={messages} />
</div>
)
}
export default LiveChatFunction;
I have added some comments of what I think you can change:
useEffect(() => {
const recieveFunction = (data) => {
//using callback so no dependency on messages
setMessages((messages) => messages.push(data));
};
async function init() {
//next line is pointless, this runs when group_id
// has changed so something must have set it
// setGroup(group_id);
await axios //not sure if this should be done before listening to socket
.post(
'http://localhost:5000/api/users/refresh_token',
null,
{ withCredentials: true }
)
.then((data) => {
if (!data.data.accessToken) {
userAuth(true);
}
});
await axios
.get('http://localhost:5000/api/users/userInfo', {
withCredentials: true,
})
.then((data) => {
setUser(data.data.user);
});
//start listening to socket after user info is set
socket.on(`message-${group_id}`, recieveFunction);
axios
.get(`http://localhost:5000/live/${group_id}`)
.then((x) => {
console.log(x.data);
});
}
init();
//returning cleanup function, guessing socket.off exists
return () =>
socket.off(`message-${group_id}`, recieveFunction);
}, [group_id]); //no messages dependencies
console.log('messages are now:',messages);
If messages is still not set correctly then can you log it
So I think I found your problem:
In your useEffect hook, you're setting messages to the wrong thing.
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
An example:
const m = [].push();
console.log(m);
// m === 0
const n = [].push({});
console.log(n);
// n === 1
As you can see this is the index.
So what you need is:
socket.on(`message-${group_id}`, data => {
messages.push(data);
setMessages(messages);
});
This will set messages to the array of messages.

Categories

Resources