Usestate not updating - reactjs

Pretty new to React Hooks and I'm running into a problem with a third party api. I'm getting the data but it's the useState hook isn't updating my state value. I'm pretty sure this is the problem bc I'm getting an error that items.map isn't a function. It does this bc there's nothing in items??? Anyone know how to deal with this?
import React, { useState, useEffect} from "react";
import axios from "axios";
const FeaturedWorks = () => {
const [items, setItems] = useState([]);
const fetchRandomData = async () => {
try {
const res = await axios(
`https://www.rijksmuseum.nl/api/en/collection?key=XXXXXXX`
);
setItems(res.data.artObjects);
console.log(res);
console.log(items);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
fetchRandomData();
},[]);
return (
<div className="featured-container">
{items.map((item, idX) => (
<h5 key={idX}>{item.title}</h5>
))}
</div>
);
};
export default FeaturedWorks;
Here's a screenshot of my response:

Since you're passing [] as the initial state for items, it's already an (empty) array on the first render. If items is not updated, items.map will still work, since .map still exists for an empty array.
So, my guess is that your setItems is indeed updating the state with the result from your query, but res.data is not an array. If res.data is and object like { values: ['foo', 'bar'] }, instead of an array like ['foo', 'bar'], then items will be set to this object and items.map() will in fact throw an error since the object must be an array for the .map function to be defined.
Does you console.log(res.data); really logs an array, or does it log something different?

Related

After useEffect API call, state set by useState for json data being passed to a component as props returns empty array

I'm still a beginner in React and I'm trying to use useEffect to fetch data from an API and then useState to set the state and then pass that state as props to a child component.
But in my child component, it appears as an empty array each time when I do console.log. I understand that on the first render the state of my initial state is an empty array []. But I've been trying to combat this and send the right JSON data but can't seem to do so.
I am trying to do this as I have multiple child components that I wanna send data to.
Below is a workaround I coded up with some digging around but doesn't work either:
const api = 'url string'
const [races, setRaces] = useState([]);
const [races2, setRaces2] = useState([races]);
useEffect(() => {
fetch(api)
.then((resp) => resp.json())
.then((response) => setRaces(response));
}, []);
useEffect(() => {
if (races.length) setRaces2(races);
}, [races]);
<Child data={races2}
But this does not seem work to work either when I do console.log(props.data) in the child component.
This is how normally one would fetch data and try and send the data but in both cases, it's been the same.
const api = 'url string'
const [races, setRaces] = useState([]);
useEffect(() => {
fetch(api)
.then((resp) => resp.json())
.then((response) => setRaces(response));
}, []);
<Child data={races}
Following is a rough flow diagram explaining what I wanna do:
Thank you for your help in advance.
I made this quick example.
Here is what the code does:
Fetching the Data using UseEffect
Storing into State
Passing the State into Component as Props
Fetching the Props and Displaying the data.
Code for App.js
import "./styles.css";
import ChildComponent from "./ChildComponent";
import { useEffect, useState } from "react";
export default function App() {
const [title, setTitle] = useState(null);
// * Init on Page Load
useEffect(() => {
fetchTitle();
}, []);
const fetchTitle = async () => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts/1"
);
const data = await response.json();
setTitle(data.title); //Setting the response into state
};
return (
<div className="App">
<ChildComponent data={title} />
</div>
);
}
Code for ChildComponent.js
export default function ChildComponent({ data }) {
return <div>{data}</div>;
}
I created this Codesandbox. This might help.
https://codesandbox.io/s/elegant-lumiere-cg66nt
Array and object are referential data types, passing as array dependency will not re-run side effect. useEffect dependencies should be primitive data type (string, number, boolean,undefined or null).
useEffect(() => {
if (races.length) setRaces2(races);
}, [races.length])// Dependencies must be primitive data type not referencial.

setData in UseEffect not populating Data

My useEffect populates tempStocksData, which is passed into setStockData() when the Promise is fulfilled. As shown in the code below, print out tempStocksData and stockData, which is supposed to be populated since I called setStockData(tempStocksData). You can see that Promise is fulfilled since it executes the prints. However, stockData is empty. For some reason setStockData is not working since stockData is not being populated. Below is the code for reference:
const [ stockData, setStockData ] = useState([])
const getStocksData = (stock) => {
return axios.get(`${BASE_URL}?symbol=${stock}&token=${TOKEN}`).catch((error) => {
console.error("Error", error.message)
})
}
useEffect(()=> {
let tempStocksData = []
const stocksList = ["AAPL", "MSFT", "TSLA", "FB", "BABA", "UBER", "DIS", "SBUX"];
let promises = [];
stocksList.map((stock) => {
promises.push(
getStocksData(stock)
.then((res) =>
{tempStocksData.push({
name: stock,
...res.data
})
})
)
})
Promise.all(promises).then(()=>{
console.log(tempStocksData)
setStockData(tempStocksData)
console.log(stockData)
})
}, [])
Please help me resolve this issue. Let me know if there is something I'm missing or something that I'm doing that is not up to date with versions/dependencies or if I'm doing Promise() js wrong.
Are you even entering your Promise.all sequence to begin with?
You are already ending the promise by having a .then function after getting the stockdata.
stocksList.map((stock) => {
promises.push(
getStocksData(stock)
)
})
Promise.all(promises).then((result)=>{
const tempStocks = result.map((stock) => {
return {
name: stock.name,
data: stock.data
}
});
console.log(tempStocksData)
setStockData(tempStocksData)
console.log(stockData)
})
Note: Above code is untested but is made to show the point
Try using the spread operator when you setStockData
like this
setStockData([...tempStocksData])
Since I've stumbled across this issue today while looking up a setData issue, let me clarify some things.
Others have pointed out that your use of promises is probably not what you actually intend to do.
Regardless, it is important to understand that a console.log of stockData inside the same useEffect that issues setStockData (even when the setter is called "before" the logging attempt) will not show the updated data in the console.
This is because all setters from useState are batched together inside useEffect calls and the corresponding getter (stockData in this case) will only reflect the updated value in the next rendering loop. It will, however, be made available when rendering or to any other hooks listening to changes to stockData.
You can find an example implementation on StackBlitz. Note that the console.log will show an empty array even though the view is updated with the API query results.
The code example from StackBlitz reproduced here:
import * as React from 'react';
import './style.css';
import { useState, useEffect } from 'react';
import axios from 'axios';
const TOKEN = 'IAPYYRPR0LN9K0K4';
const BASE_URL = 'https://www.alphavantage.co/query?function=GLOBAL_QUOTE';
export default function App() {
const [stockData, setStockData] = useState([]);
const getStocksData = (stock: string) => {
return axios
.get<{ 'Global Quote': { [data: string]: string } }>(
`${BASE_URL}&symbol=${stock}&apikey=${TOKEN}`
)
.then((result) => result.data)
.catch((error) => {
console.error('Error', error.message);
});
};
useEffect(() => {
const stocksList = ['AAPL', 'MSFT', 'TSLA'];
let promises: Promise<void | {
'Global Quote': { [data: string]: string };
}>[] = [];
stocksList.map((stock) => {
promises.push(getStocksData(stock));
});
Promise.all(promises).then((result) => {
setStockData(result);
console.log(stockData);
});
}, []);
return <pre>{JSON.stringify(stockData, undefined, ' ')}</pre>;
}

React hooks async problems: map is executing before data is returned - solutions?

In the code below, you can see that I'm mapping over data returned from an axios GET request. I'm then passing this data through a filter, and setting it to state (under the variable gig).
I'm then wanting to map over the data held in gig - only problem is that when I try, I get an error say that TypeError: gig.map is not a function, and gig console logs to undefined.
However, when gig is console logged inside the useEffect method, it returns the data I want.
So I'm guessing that what is happening is that setState is aysnc, and the gig.map function is being reached before the gig has been set to filteredGigs.
Any suggestions on how to fix this?
Here's the full code:
import React, {useState, useEffect} from 'react'
import axios from 'axios'
import { auth } from 'firebase/app'
const UniqueVenueListing = (props) => {
const [gig, setGig] = useState([])
const authUserId = props.userDetails.uid
useEffect(()=>{
axios.get("https://us-central1-gig-fort.cloudfunctions.net/api/getGigListings")
.then(res => {
let filteredGigs = res.data
.filter(gig => {
return gig.user !== authUserId
})
setGig({gig: filteredGigs})
console.log(gig)
})
},[])
useEffect(() => {
console.log(gig)
}, [gig])
return(
<div>
{
gig.map(gigs => {
return gigs.user
})
}
</div>
)
}
export default UniqueVenueListing
Issue
You change the state shape. Initial shape of gig state is an empty array([]), but in the effect you store an object with an array under key gig ({ gig: filteredGigs }). Additionally since state updates are asynchronous, the console.log after setGig will only log the current state, not the one just enqueued.
Solution
Just save the filtered gig array into state. This will keep the gig state an array and later in the return gig.map(... will work as expected.
useEffect(()=>{
axios.get("https://us-central1-gig-fort.cloudfunctions.net/api/getGigListings")
.then(res => {
const filteredGigs = res.data.filter(gig => {
return gig.user !== authUserId
})
setGig(filteredGigs); // <-- store the array in state
})
},[])

How come my state is not updated after fetching data?

Good morning everyone,
I just finished my training in React and I wanted to make a project to practice.
To start, i'm juste posting data in a firebase database.
Then, i would like to fetch data to display it in a react component using hooks (useState and useEffect).
The problem is i end up in the classic trap of infinite loop after fetching datas with axios, but i can't figure out how to stop it with dependency.
Please find code below :
import React, {useState} from 'react'
import {useEffect} from 'react'
import classes from './Incomes.module.css'
import axios from '../../../axios'
import Button from '../../UI/Button/Button'
import Input from '../../UI/Input/Input'
const Incomes = (props) => {
const [inputValue, setInputValue] = useState('');
const [fetchedTypes, setFetchedTypes] = useState([]);
const onClickHandler = () => {
const type = {type: inputValue}
axios.post('/types.json', type)
.then(response => {
alert('ok c good');
})
.catch(error => console.log(error))
}
const onChangeHandler = (event) => {
setInputValue(event.target.value)
}
useEffect(() => {
axios.get('/types.json')
.then(response => {
let types = []
for(let key in response.data) {
types.push({...response.data[key], id: key})
}
console.log('types : ', types)
setFetchedTypes(prevFetchedTypes => [...prevFetchedTypes, types])
})
.catch(error => console.log(error))
console.log('fetchedtypes', fetchedTypes)
}, [fetchedTypes])
return (
<div className={classes.Incomes}>
<h2>Paramètres des entrées</h2>
<h3>Ajouter un type de revenu</h3>
<Input type="text" disabled={false} change={onChangeHandler} placeholder="Nom du champ"></Input>
<Button onClick={onClickHandler} type="Normal" clicked={onClickHandler}>Ajouter</Button>
<h3>Liste des types de revenus disponibles</h3>
</div>
)
}
export default Incomes;
When i console log types, the datas are correct.
But when i console log fetchedTypes, it's an empty array.
I found this post but it's not working neither :
React useEffect infinite loop fetch data axios
I'm not sure if it's an issue with the way I use useEffect or the way I update state, or both.
Thank you for your help
Have a good day
The reason you're getting an infinite loop is because of your useEffect and for two reasons.
I'm assuming fetchedTypes is not a primitive but some object/array so it'll always be different every time. Don't use it with useEffect. If you want to depend on objects, go for useDeepCompareEffect by Kent but you don't need it here.
The second and main reason for your infinite loop is the statement:
setFetchedTypes(prevFetchedTypes => [...prevFetchedTypes, types])
Your useEffect is updating fetchedTypes with the above line but it's also meant to run when fetchedTypes changes. So useEffect updates it and since it's a dependency which has changed, useEffect runs again resulting to an infinite loop.
A possible solution:
If you want to fetch data when your component loads, change your useEffect to this:
useEffect(() => {
axios.get('/types.json')
.then(response => {
let types = []
for(let key in response.data) {
types.push({...response.data[key], id: key})
}
console.log('types : ', types)
setFetchedTypes(types)
})
.catch(error => console.log(error))
console.log('fetchedtypes', fetchedTypes)
}, [])
Ignore any lint warning. An empty dependency array means this will run only once when the component renders.
But since you have an on click handler, I'm assuming you want to update the data after the POST request. If your onClickHandler returns the newly created type then do this:
const onClickHandler = () => {
const type = {type: inputValue}
axios.post('/types.json', type)
.then(response => {
const newType = response.data;
const updatedTypes = [...types, newType]
setFetchedTypes(updatedTypes)
})
.catch(error => console.log(error))
}
NOTE: I don't know the structure of your types so I'm working with assumptions but that's the idea

how to make an object with multiple object an array in state?

This is my code,
import React, { useState, useEffect } from "react";
import axios from "axios";
import "./App.css";
function App() {
let [albums, setAlbums] = useState([]);
useEffect(() => {
const key = "blablabla to keep secret";
const fetchData = async () => {
const result = await axios(
`http://ws.audioscrobbler.com/2.0/?method=artist.gettopalbums&artist=cher&api_key=${key}&limit=10&format=json`
);
setAlbums(result.data.topalbums);
console.log(albums, "data?");
// const { data } = props.location.state;
};
fetchData();
}, []);
return <div className="App"></div>;
}
export default App;
the data I am fetching is in an object with objects inside, in-state I initialized with an empty array, and then after I fetched the data I use setAlbums. I have two problems, the console.log after I fetch just shows me an empty array, but when I console log in render I do get the data but it is an object and not an array of objects, so I can't even map over it, how do I fix this?
Try to do something like this:
setAlbums(album => [...album, results.data.topalbums])
that way you can push results to your array instead of transforming it into object
also if you wish to see updated album then create something like:
useEffect(() => {
console.log(albums)
},[albums])
as setting state is asynchronous therefore it doesn't happen immediately as Brian mentioned, so this code will launch if albums state changes its value

Resources