Multiple useEffect not working as expected - javascript

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.

Related

How to avoid unnecessary API calls with useEffect?

I'm still beginner to ReactJS and I'm having trouble rendering a list.
I don't know why, all the time calls are being made to my API. Since I don't put any dependency on useEffect, that is, I should only render my function once.
I don't understand why this is happening. Can you tell me what I'm doing wrong?
Here's my code I put into codesandbox.io
import React from "react";
import axios from "axios";
import "./styles.css";
const App = () => {
const BASE_URL = "https://pokeapi.co/api/v2";
const [pokemons, setPokemons] = React.useState([]);
const getAllPokemons = async () => {
const { data } = await axios.get(`${BASE_URL}/pokemon`);
data.results.map((pokemon) => getPokeType(pokemon));
};
const getPokeType = async (pokemon) => {
const { data } = await axios.get(pokemon.url);
setPokemons((prev) => [...prev, data]);
};
React.useEffect(() => {
getAllPokemons();
}, []);
console.log(pokemons);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{pokemons.map((pokemon) => (
<p key={pokemon.id} style={{ color: "blue" }}>
{pokemon.name}
</p>
))}
</div>
);
};
export default App;
Thank you very much in advance.
Your issue is that you are calling setPokemons inside getPokeType (which is called for each data in part). Your useEffect is called just once (as expected) and the ${BASE_URL}/pokemon call is executed just once too. But getPokeType is called 20 times and the pokemons state is changed 20 times as well (once for each instance from data.results).
What I would recommend in your case (instead of what you have now) is:
Create a list of all the pokemons and
Set the state just once at the end.
So something like:
...
const getPokeType = async (pokemon) => {
const { data } = await axios.get(pokemon.url);
return data;
};
const getAllPokemons = async () => {
const { data } = await axios.get(`${BASE_URL}/pokemon`);
const pokemons = await Promise.all(
data.results.map((pokemon) => getPokeType(pokemon))
);
setPokemons(pokemons);
};
React.useEffect(() => {
getAllPokemons();
}, []);
...
I was just having the same issue in my project the way I solved is by moving the function definition inside the useEffect
React.useEffect(() => {
const getAllPokemons = async () => {
const { data } = await axios.get(`${BASE_URL}/pokemon`);
data.results.map((pokemon) => getPokeType(pokemon));
};
getAllPokemons();
}, []);
If this solves your problem please accept the answer.

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.

Cleaning component states useEffect

I have states :
const { id } = useParams<IRouterParams>();
const [posts, setPosts] = useState<IPost[]>([]);
const [perPage, setPerPage] = useState(5);
const [fetchError, setFetchError] = useState("");
const [lastPostDate, setLastPostDate] = useState<string | null>(null);
// is any more posts in database
const [hasMore, setHasMore] = useState(true);
and useEffect :
// getting posts from server with first render
useEffect(() => {
console.log(posts);
fetchPosts();
console.log(hasMore, lastPostDate);
return () => {
setHasMore(true);
setLastPostDate(null);
setPosts([]);
mounted = false;
return;
};
}, [id]);
When component change (by id), I would like to clean/reset all states.
My problem is that all states are still the same, this setState functions in useEffect cleaning function doesn't work.
##UPDATE
// getting posts from server
const fetchPosts = () => {
let url;
if (lastPostDate)
url = `http://localhost:5000/api/posts/getPosts/profile/${id}?limit=${perPage}&date=${lastPostDate}`;
else
url = `http://localhost:5000/api/posts/getPosts/profile/${id}?limit=${perPage}`;
api
.get(url, {
headers: authenticationHeader(),
})
.then((resp) => {
if (mounted) {
if (resp.data.length === 0) {
setFetchError("");
setHasMore(false);
setPosts(resp.data);
return;
}
setPosts((prevState) => [...prevState, ...resp.data]);
if (resp.data.length < perPage) setHasMore(false);
setLastPostDate(resp.data[resp.data.length - 1].created_at);
setFetchError("");
}
})
.catch((err) => setFetchError("Problem z pobraniem postów."));
};
if your component isnt unmounted, then the return function inside useEffect will not be called.
if only the "id" changes, then try doing this instead:
useEffect(() => {
// ... other stuff
setHasMore(true);
setLastPostDate(null);
setPosts([]);
return () => { //...code to run on unmount }
},[id]);
whenever id changes, the codes inside useEffect will run. thus clearing out your states.
OK, I fixed it, don't know if it is the best solution, but works...
useEffect(() => {
setPosts([]);
setHasMore(true);
setLastPostDate(null);
return () => {
mounted = false;
return;
};
}, [id]);
// getting posts from server with first render
useEffect(() => {
console.log(lastPostDate, hasMore);
hasMore && !lastPostDate && fetchPosts();
}, [lastPostDate, hasMore]);

React-Native infinite loop

I am trying to get data from my firebase-firestore I an showing a loading state to wait for the data to load however when it does load it keeps returning the firestore data infinite times. Please may someone help me.
This is my code Paper is just a custom component
import Paper from '../Components/Paper'
import firebase from 'firebase'
import { useState } from 'react'
const Home = (props) => {
const renderMealItem = (itemData) =>{
return (
<Paper
title={itemData.item.name}
serves={itemData.item.servings}
time={itemData.item.time}
image={itemData.item.imageUri}
/>
)
}
const [loading, setLoading] = useState(false)
const [all, setAll] = useState([])
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
},[])
const checkReturn = () => {
if(all !== undefined){
setLoading(false)
}
}
const getUser = async() => {
try {
await firebase.firestore()
.collection('Home')
.get()
.then(querySnapshot => {
querySnapshot.docs.forEach(doc => {
setAll(JSON.stringify(doc.data()));
});
});
}catch(err){
console.log(err)
}
}
return(
<View style={styles.flatContainer}>
<FlatList
data={all}
keyExtractor={(item, index) => index.toString()}
renderItem={renderMealItem}/>
</View>
)
}
useEffect without second parameter will get executes on each update.
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
})
so this will set the loading and tries to get the user. and when the data comess from server, it will get runned again.
So you should change it to : useEffect(() => {...}, []) to only get executed on mount phase(start).
Update: you should check for return on every update, not only at start. so change the code to:
useEffect(() => {
setLoading(true)
getUser()
}, [])
useEffect(() => {
checkReturn()
})
Ps: there is another issue with your code as well:
querySnapshot.docs.forEach(doc => {
setAll(JSON.stringify(doc.data()));
});
maybe it should be like :
setAll(querySnapshot.docs.map(doc => JSON.stringify(doc.data())));
Try passing an empty array as an argument to useEffect like so:
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
}, [])

Data initialization in useEffect triggers multiple requests

This is a follow-up question to this one.
Simply, I'm fetching the same date in two different ways. One by one and all together for updates. I have a simple store with context and useReducer.
My current code is like that:
import React, { useEffect } from "react";
import axios from "axios";
import { useGlobalState } from "./state";
const arr = Array.from(Array(100), (x, i) => i + 1);
function App() {
const [{ posts, init }, dispatch] = useGlobalState();
useEffect(() => {
const getInc = () => {
arr.forEach(async id => {
const res = await axios(
`https://jsonplaceholder.typicode.com/posts/${id}`
);
dispatch({
type: "INC",
payload: res.data
});
});
};
const getAll = async () => {
const promises = arr.map(id =>
axios(`https://jsonplaceholder.typicode.com/posts/${id}`)
);
const res = await Promise.all(promises);
dispatch({
type: "ALL",
payload: res.map(el => el.data)
});
};
if (init) {
getInc();
} else {
getAll();
}
setInterval(() => getAll(), 10000);
}, [dispatch, init]);
return (
<>
<div>{posts.length}</div>
</>
);
}
export default App;
In every interval getAll is triggered twice.Here is a working sandbox.
I added a console.log to reducer part so you can see it runs twice. I can also see it in the network tab.
Try Separate Concerns when using useEffect, like you mentioned "One by one and all together for updates".
function App() {
const [{ init, posts }, dispatch] = useGlobalState();
useEffect(() => {
setInterval(() => getAll(dispatch), 10000);
}, [dispatch]);
useEffect(() => {
init ? getInc(dispatch) : getAll(dispatch);
}, [init, dispatch]);
...
}
Note:
useEffect(() => {
init ? getInc(dispatch) : getAll(dispatch);
}, [init, dispatch]);
After init turns to true, getAll gets called twice, once from upcoming interval and one from the useEffect above.
So in summary, on your Network, first 3 post request intervals with ids [0-99] are:
from getInc
from getAll in useEffect with init
from getAll in interval

Categories

Resources