Saving api response to State using useState and Axios (React JS) - javascript

I'm having an issue when trying to save to State an axios API call. I've tried
useState set method not reflecting change immediately 's answer and many other and I can't get the state saved. This is not a duplicate, because I've tried what the accepted answer is and the one below and it still doesn't work.
Here's the (rather simple) component. Any help will be appreciated
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // returns '[]'
});
}, []); // If I set 'widgets' here, my endpoint gets spammed
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};

Welcome to stackoverflow, first thing first the setting call is incorrect you must use spread operator to combine to array into one so change it to setWidgets([...widgets, ...data]); would be correct (I assume both widgets and data are Array)
second, react state won't change synchronously
.then((response) => {
const data = response.data;
console.log(data); // returns correctly filled array
setWidgets(widgets, data);
console.log(widgets); // <--- this will output the old state since the setWidgets above won't do it's work till the next re-render
so in order to listen to the state change you must use useEffect hook
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
this will console log anytime widget changes
the complete code will look like this
export const Home = () => {
const [widgets, setWidgets] = useState([]);
useEffect(() => {
axios
.get('/call-to-api')
.then((response) => {
const data = response.data;
setWidgets([...widgets, ...data])
});
}, []);
useEffect(() => {
console.log("Changed Widgets: ", widgets)
}, [widgets])
return (
<Fragment>
{/* {widgets.map((widget) => { // commented because it fails
<div>{widget.name}</div>;
})} */}
</Fragment>
);
};

Try:
setWidgets(data);
istead of
setWidgets(widgets, data);

Your widgets.map() probably fails because there isn't much to map over when the component is being rendered.
You should update it with a conditional like so, just for clarity:
widgets.length>0 ? widgets.map(...) : <div>No results</div>
And your call to setWidgets() should only take one argument, the data:
setWidgets(data)
or if you want to merge the arrays use a spread operator (but then you need to add widgets as the dependency to the useEffect dependency array.
setWidgets(...widgets, ...data)
You might also have to supply the setWidgets hook function to the useEffect dependency array.
Let me know if this helps..

Related

React.js useEffect alternative or another hook maybe?

i have another "movie database" application in react. At the mount it renders movies based on api key which is set to "new movies". Then i have useEffect which update movie list based on searchbar and its value. Problem is, it renders new movies and just after that renders movies based on searchbar value which is empty. I know that useEffect is running on mount. What is best practice to use it this way? Or is there any better hook for this particular use? Thank you.
React.useEffect(() => {
fetch(
`https://api.themoviedb.org/3/search/movie?api_keylanguage=en-US&query=${searchValue}&`
)
.then((res) => res.json())
.then((data) => {
setMovies(data.results);
});
}, [searchValue]);
Hey #JSpadawan best practice is to use filter like-
const [data,setData] = useState()
useEffect(()=>{
const apiData = fetch(APILINK).then((res)=>res.json()).catch(err)
if(apiData.length>0){
setData(apiData)
}
},[])
This will set the data of api.. now use filter like-
const [searchValue, setSearchValue]= useState()
const filterData = data && data.filter((data)=>data.includes(searchValue))
After this you can use filterdata any where you want. If you still facing issue just lemme know, i will help you more.
Thanks
You can set the movies with the useEffect, but I would recommend having a secondary useEffect that would be for the seach bar. You should try to avoid making tons of API calls as the more you make, the more it will slow down the application. So have one useEffect that will set the data, and a second one that you can filter out the ones that match the search bar.
If the searchValue is updated every key stroke then searching for a movie with a long title would create tons of API calls. You could put the fetch call in an async/await function so you get the data, then set it, then you can filter with a different effect.
useEffect(() => {
const getData = async () => {
await fetch(
`https://api.themoviedb.org/3/search/movie?api_keylanguage=en-US&query=${searchValue}&`
)
.then((res) => res.json())
.then((data) => {
setMovies(data.results);
});
}
getData();
}, []); // empty array will run once on component load, or add a dependency of when you want it to re-call the api
Then a second useEffect for filtering
useEffect(() => {
let searchTextFilter = movies.filter(i => {
return (!searchValue || (searchValue && i.Title.toLowerCase().includes(searchFilter?.toLowerCase() )))
});
}, [searchValue])

In React, how can I make a single api call, set the result to a state variable, and display it?

I am programming a react application where I need to make a single async api call, save the result to a state variable and display the result. I am using an axios instance where the response of the api call is a nested object. For example:
{
kanji:
{character: "", stroke:""},
quote: "string"
}
So far I have the following code where I am able to console.log the homeData object successfully. However I get the error: 'TypeError: homeData is undefined'.
const Home = () =>{
const [homeData, setHomeData] = useState({})
const getHomeData = async()=>{
instance.get("/data")
.then(res =>{
setHomeData(res.data)
})
.catch(error =>{
console.log(error)
})
}
useEffect(()=>{
getHomeData()
}, [])
console.log(homeData)
return(
<>
<p>{homeData}<p>
</>
)
}
This seems like a promise issue but I am not sure how to fix it. Thank you for your help in advance.
This is not a promise issue, it has to due with the order in which the lifecycle methods run. useEffect runs after the component renders. This means when the component first renders, the homeData is an empty object. homeData will be present in the second render. The following code first checks if homeData exists, then if it exists, it displays the kanji character. Note also, you cant just display a raw object into the dom so you will have to access the homeData by property for it to display
const Home = () =>{
const [homeData, setHomeData] = useState({})
const getHomeData = async()=>{
instance.get("/data")
.then(res =>{
setHomeData(res.data)
})
.catch(error =>{
console.log(error)
})
}
useEffect(()=>{
getHomeData()
}, [])
console.log(homeData)
return(
<>
<p>{homeData && homeData.kanji.character}<p>
</>
)
}

useEffect break array of useState at first time

I am learning react hooks. I am having mock data js call "MockFireBase.js" as below:
const userIngredientsList = [];
export const Get = () => {
return userIngredientsList;
}
export const Post = (ingredient) => {
ingredient.id = userIngredientsList.length + 1;
userIngredientsList.push(ingredient);
return ingredient;
}
Then my react hooks component "Ingredients.js" will call this mock utilities as following details:
const Ingredients = () => {
const [userIngredients, setUserIngredients] = useState([]);
// only load one time
useEffect(() => { setUserIngredients(Get()); }, []);
const addIngredienHandler = ingredient => {
let responsData = Post(ingredient);
setUserIngredients(preIngredients => {
return [...preIngredients, responsData]
});
}
return (
<div className="App">
<IngredientForm onAddIngredient={addIngredienHandler} />
<section>
<IngredientList ingredients={userIngredients} />
</section>
</div>
);
)
}
When I added first ingredient, it added two (of course I get same key issue in console.log). Then I added second ingredient is fine.
If I remove the useEffect code as below, it will work good.
// only load one time
useEffect(() => { setUserIngredients(loadedIngredients); }, []);
I am wondering what I did anything wrong above, if I use useEffect
The problem is not in useEffect. It's about mutating a global userIngredientsList array.
from useEffect you set initial component state to be userIngredientsList.
Then inside addIngredienHandler you call Post(). This function does two things:
2a. pushes the new ingredient to the global userIngredientsList array`. Since it's the same instance as you saved in your state in step 1, your state now contains this ingredient already.
2a. Returns this ingredient
Then, addIngredienHandler adds this ingredient to the state again - so you end up having it in the state twice.
Fix 1
Remove userIngredientsList.push(ingredient); line from your Post function.
Fix 2
Or, if you need this global list of ingredients for further usage, you should make sure you don't store it in your component state directly, and instead create a shallow copy in your state:
useEffect(() => { setUserIngredients([...Get()]); }, []);

Why my axios get not always working in React

I try to fetch data from blogger api.
const NajnowszeWpisy = () => {
const [articles, setArticles] = useState([]);
useEffect(() => {
axios
.get(
'https://www.googleapis.com/blogger/v3/blogs/****/posts?key=****'
)
.then((res) => {
setArticles(res.data.items);
});
});
Then check if there are any articles (in my blog).
Check if there is any image in blog content, if yes, convey it to props.
if (articles.length) {
console.log('get');
let image = '';
const lastPost = articles[articles.length - 1];
if (lastPost.content.match(/<img\s+[^>]*src="([^"]*)"[^>]*>/i)) {
image = ReactHtmlParser(
lastPost.content.match(/<img\s+[^>]*src="([^"]*)"[^>]*>/i)[0]
);
}
return (
<StyledContainer>
<Post
image={
image ? (
image
) : (
<img src={defaultImage} alt="default-img" />
)
}
title={lastPost.title ? lastPost.title : 'Nowy Artykuł'}
content={lastPost.content}
id={lastPost.id}
key={lastPost.id}
/>
</StyledContainer>
);
} else {
console.log('no get');
}
};
In response i get error GET 429, so there are too many requests.
Also I can't get any of my console.log messages ('get' or 'no get').
Can you tell me if I missed something?
You should try to pass an empty array as second argument to useEffect to run your function only once after initial render:
useEffect(() => {
axios
.get(
'https://www.googleapis.com/blogger/v3/blogs/****/posts?key=****'
)
.then((res) => {
setArticles(res.data.items);
});
}, []);
From React docs:
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.

The component does not work asynchronously

I have a component that makes a request and displays a list of jobs.
import React, { useState, useEffect, Fragment } from 'react';
import { Jobs } from '../components/Jobs.component';
export const Fixed = () => {
const [jobs, setJobs] = useState([]);
useEffect(() => {
getItems();
}, []);
async function getItems() {
const url = 'http://localhost:8081/api/fixed/list';
const res = await fetch(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
});
const data = await res.json();
console.log(data);
setJobs(data.jobsList);
console.log(jobs);
}
return (
<Fragment>
{jobs.map(job => (
<div>
<Jobs job={job} />
</div>
))}
</Fragment>
);
};
My problem is that the first console outputs an array of jobs, but the second console displays an empty array. And an empty value is passed to the job component, which causes an error.
He does not have time to write the work in a state? Where am I wrong?
Method setJobs needs some time to change state so console.log runs faster than value changes.
You should render list if the array length is bigger than 0.
{jobs.length && jobs.map(job => <Jobs job={job} />)}
State updates are run asynchroniously
The reason your console.log shows an empty array is because setJobs runs asynchroniously and will update jobs value on next render. Looking at react setState documentation (same as useState react hooks) :
setState() enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state.
And so
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.
const ... jobs ... is a constant - it will be a different constant in 2 different renders, but it will not change value during a single render
The jobs inside getItems is a closure and will reference to the value from the first render, while setJobs will only change the value in second render.
It's similar to the following:
const rememberJobs = (jobs) => () => console.log(jobs)
const first = rememberJobs([])
const second = rememberJobs([1, 2, 3])
first()
second()

Categories

Resources