Unable to access properties of object stored in a React state - reactjs

I am using React hooks and I need to store the data from my server which is an object, to a state.
This is my state:
const [sendingTime,setSendingTime] = useState({})
This is how I set it inside useEffect:
const getTime= () =>{
axios.get("https://localhost:1999/api/getLastUpdatedTime")
.then(res => {
console.log(res.data)
setSendingTime({sendingTime : res.data})
console.log('sendingTime is coimng',sendingTime)
})
.catch(err => console.error(err))
}
useEffect(() => {
getCount()
getTime()
},[])
Now when I console.log the object state it returns an empty object. Although if I set the object to any variable and then console.log it it doesn't return an empty object. But both ways I am unable to access the properties of the object.
Edit
This is what I was doing previously:
const[time,setTime] = useState({
totalComplaintsTime: '00:00',
resolvedComplaintsTime: '00:00',
unresolvedComplaintsTime:'00:00',
assignedComplaintsTime:'00:00',
supervisorsTime:'00:00',
rejectedComplaintsTime:'00:00'
})
const getTime= () =>{
axios.get("https://localhost:1999/api/getLastUpdatedTime")
.then(res => {
console.log(res.data)
setTime({
totalComplaintsTime: res.data.Complaints[0].updatedAt,
resolvedComplaintsTime: res.data.Resolved[0].updatedAt,
unresolvedComplaintsTime: res.data.Unresolved[0].updatedAt ,
assignedComplaintsTime: res.data.Assigned[0].updatedAt ,
rejectedComplaintsTime: res.data.Rejected[0].updatedAt,
supervisorsTime: res.data.Supervisors[0].updatedAt
})
})
.catch(err => console.error(err))
}
useEffect(() => {
getCount()
// getTime()
getTime()
},[])
And this is how I used the states to set the dynamic values :
Last updated at {time.resolvedComplaintsTime}
This is working perfectly fine but I wanted to store the data in an object state and then access it, which would've been easier and more efficient. Also I wanted to pass this object to another component. That is why I wanted to make a state and store the data in that state.
Solved
So the main problem was accessing the data throughout the component. This is the solution:
sendingTime is being initialized but only when a render occurs. So we add a piece of code to check if that state is initialized or not.
This is where I wanted to display the data.
<div key={sendingTime.length} className={classes.stats}>
<UpdateIcon fontSize={"small"} /> Last updated at{" "}
{Object.keys(sendingTime).length > 0 &&
sendingTime.Complaints[0].updatedAt}
</div>
This way I can access the properties of the object stored in the sendingTime state very easily.

setSendingTime comes from a useState so it is asynchronous:
When you call:
setSendingTime({sendingTime : res.data})
console.log('sendingTime is coimng',sendingTime)
The state sendTime has not been updated, so it display the init value which is {}

The other answers are correct, setState is asynchronous so you will only be able to get sendingTime's new value on the next re-render. And as #Enchew mentions, you probably don't want to set an object as that value most likely.
Try this instead:
const [data, setData] = useState(undefined)
const getTime = () => {
axios.get("https://localhost:1999/api/getLastUpdatedTime")
.then(({ data }) => {
console.log(data)
setData(data)
})
.catch(err => console.error(err))
}
useEffect(() => {
getCount()
getTime()
}, [])
if (!data) return <p>No data...</p>
return (
<>
<p>Complaints: {data.Complaints[0].updatedAt}<p>
<p>Resolved: {data.Resolved[0].updatedAt}<p>
<p>{/* ...etc... */}</p>
</>
)
You should see the value you're expecting to see in the console.log because you're using the locally scoped variable, not the asynchronous value, which will only be updated on the next re-render.

setSendingTime works asynchronously and your object may have not been saved in the state yet. Also pay attention to the way you save the time in your state, you are wrapping the result of the data in another object with property sendingTime, which will result in the following object: sendingTime = { sendingTime: {/*data here */} }. If you are running for sendingTime: {/*data here */} try the following:
const getTime = () => {
axios
.get('https://localhost:1999/api/getLastUpdatedTime')
.then((res) => {
console.log(res.data);
setSendingTime(res.data);
console.log('sendingTime is coimng', sendingTime);
})
.catch((err) => console.error(err));
};
Also have a look at this - how to use hooks with a callback: https://www.robinwieruch.de/react-usestate-callback

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.

Axios returns object but useState hook wont set

Ive searched for problems like this but haven't found solution yet. When I console.log(data) I see the object with all the proper data. When i try to access it with data.name(or any other property on the object) nothing happens, even intellisense doesn't have anything for it.
const key = process.env.REACT_APP_WEATHER_API_KEY;
const [data, setData] = React.useState<{}>({});
const url = `https://api.openweathermap.org/data/2.5/weather?q=Lethbridge&units=imperial&appid=${key}`;
const getWeatherData = async () => {
if (longitude && latitude !== undefined) {
axios
.get(url)
.then(response => {
const returnedData = response.data;
setData(returnedData);
})
.catch(err => console.log('Error:', err));
}
};
React.useEffect(() => {
getWeatherData();
console.log('data', data);
}, []);
When i console.log('data') though i see the object returned from api request proper like this.
Console logged return from api
You are fetching some data that is not typed (or at least you are not using the type in your code. You are getting data from axios that is typed as any, because axios doesn't know anything about its type. Now when you use that data to set it to the state with setData, the data will take the type you have given to the state, in this case {}, an empty object, hence why you can't access any properties.
You need to shape that object, declare a type for the data you are receiving and set it to the state. If you declare the state like this, you will be able to access the property name:
const [data, setData] = React.useState<{name: string}>({});

Results from realtime db not available when called

Im having some trouble with realtime database and react native.
I have the following useEffect in a component that is supposed to listen for changes and then update state as required, I then use that state to populate a list.
The component gets a data object passed as a prop, the data object contains a string array called members that contains uuids, I am trying to iterate over those to get the attached user from realtime db and then save those objects to a state array.
const myComponent = ({ data }) => {
const [users, setUsers] = useState([]);
useEffect(() => {
const userArr = [];
data.map(item => {
item.members.forEach((username: string) => {
database()
.ref(`users/${username}`)
.on('value', snapshot => {
userArr.push(snapshot.val());
});
});
});
setUsers(userArr);
};
}, []);
return (
<>
{users} <----- this is in a flatlist
</>
);
}
It works eventually after refreshing the screen about 5 times. Any help would be greatly appreciated.
The simplest way to get some data to show is to move update the state right after you add an item to the array:
useEffect(() => {
const userArr = [];
data.map(item => {
item.members.forEach((username: string) => {
database()
.ref(`users/${username}`)
.on('value', snapshot => {
userArr.push(snapshot.val());
setUsers(userArr); // 👈
});
});
});
};
}, []);
Now the UI will update each time that a user is loaded from the database.
I also recommend reading some more about asynchronous loading, such as in Why Does Firebase Lose Reference outside the once() Function?
Your database call may be asynchronous, which is causing the code inside the useEffect to act a little funny. You could push all those database calls (while iterating through item.members) into an array, and then do Promise.all over the array. Once the promises are resolved, you can then set the users.
Hope this helps!
add an async function inside useEffect and call it
useEffect(() => {
const getUsers = async () => {
const userArr = [];
data.....
//wait for it with a promise
Promise.all(userArr).then(array => setUsers(array))
})
getUsers()
}, [])
not sure if the function needs to be async

Why is my React fetch weather API so buggy/temperamental?

I'm trying to fetch weather API after setting input onChange and button onClick. It kept returning undefined when I tried to .then (data => {setWeather(data)})
So I applied a solution from a youtube tutorial that uses a conditional operator to display component that goes as below in my App.
This basically worked, but it's so buggy and sensitive that even if I just add a className for some css in WeatherDisplay.jsx it crashes and starts returning undefined, much less any of the other things I need to do to it to get it looking right.
Would be grateful if anyone could tell me why, or even better provide a better alternative?
Edit: It now just no longer works at all and continues to return undefined even when I've reverted it to the code that was working
function handleClick(query) {
fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${query}&units=${unit}&appid=${apiKey}`,
)
.then((response) => response.json())
.then((data) => {
setWeather(data);
console.log(data);
});
setWeatherReturn({
type: weather.weather[0].description,
temp: weather.main.temp,
image: weather.weather[0].icon,
});
}
{
typeof weather.main != "undefined" ? (
<WeatherDisplay
weatherType={weatherReturn.type}
weatherTemp={weatherReturn.temp + "°C"}
weatherImg={`http://openweathermap.org/img/wn/${weatherReturn.image}#2x.png`}
/>
) : (
""
);
}
It looks like you're using setWeatherReturn to process the weather data outside the then chain, so it's likely never loaded at that point.
The simple fix is just moving the setWeatherReturn within that callback:
function handleClick(query) {
fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${query}&units=${unit}&appid=${apiKey}`,
)
.then((response) => response.json())
.then((data) => {
setWeather(data);
setWeatherReturn({
type: data.weather[0].description,
temp: data.main.temp,
image: data.weather[0].icon,
});
});
}
Either way, you shouldn't use state for data derived from state (your weatherReturn), since it's then very easy to have that derived data be out of sync with the state it's derived from (e.g. you call setWeather and forget to call setWeatherReturn); instead use useMemo.
With that in mind, a simple stand-alone component that loads weather information and formats it might look like this.
The idea is that useEffect is automatically triggered when the query or unit changes, and the useMemo similarly when the raw data gets loaded.
If the data isn't yet loaded, the useMemo callback will return undefined, so the component itself just says "loading".
const apiKey = '...';
function WeatherLoader({ query, unit }) {
const [data, setData] = React.useState(null);
React.useEffect(() => {
fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${query}&units=${unit}&appid=${apiKey}`,
)
.then((response) => response.json())
.then(setData);
}, [query, unit]);
const formattedWeatherData = React.useMemo(() => {
if (!data) return undefined;
return {
type: weather.weather[0].description,
temp: weather.main.temp,
image: weather.weather[0].icon,
};
}, [data]);
if (!formattedWeatherData) return <>Loading...</>;
const { type, temp, image } = formattedWeatherData;
return (
<WeatherDisplay
weatherType={type}
weatherTemp={`${weatherReturn.temp}°C`}
weatherImg={`http://openweathermap.org/img/wn/${image}#2x.png`}
/>
);
}
You would use this e.g.
<WeatherLoader query="New York" unit="C" />
(or whatever unit takes as a value).

State variable is not getting updated using react hook

I am learning ReactJS and trying to update the parent props with the updated state of ingredients from the child component. The setUserIngredients is called and updated ingredients are being passed to parent.
Code :
const [userIngredients, setUserIngredients] = useState([]);
const removeIngredientHandler = id => {
setLoading(true);
fetch(`https://***************.com/ingredients/${id}.json`,{
method:'DELETE'
}).then(response=>{
setLoading(false);
setUserIngredients(prevIngredients =>
prevIngredients.filter(ingredient =>{
return (ingredient.id !== id)
//return ingredient;
})
);
**props.ingredients(userIngredients);**
//userIngredients is still having old value
//need to check on this
}).catch(error => {
setError(error.message);
})
};
The problem is that userIngredients is a variable that is created when the component renders, set to a version fo the state when that component renders. And when you start an asynchronous operation (like a fetch) the callback you pass to that operation will be bound the values from when that callback was created.
The fix here is pretty simple. In the spot where you calculate your new ingredients, simply execute whatever callback you need before returning the value to be stored in the state.
Something like:
fetch(`https://***************.com/ingredients/${id}.json`, {
method: 'DELETE',
}).then(response => {
setLoading(false)
setUserIngredients(prevIngredients => {
// Figure out the new ingredients
const newIngredients = prevIngredients.filter(ingredient => ingredient.id !== id)
// Call your callback with the new ingredients
props.ingredients(newIngredients)
// Return the new ingredients to be stored in your state
return newIngredients
})
})

Resources