Can't access state in functional component in Reacht Native - reactjs

I'm currently trying to build an app in React Native. Unfortunately, I'm having a hard time understanding the state management in functional components.
The fetch resolves successfully and gives me an array of activities and I store them in the component's state. But after that I want to make more fetches regarding these activities and for that i need to know each activities' ID, so I have to access the state. This doesn't work though, as there's only an empty array printed to the console from the last log.
From the printed timestamps I can see that everything executes in the desired order and I can easily access the state in other places and get the full array of activities, but why isn't it working here?
Here's the code:
const [activities, setActivities] = useState([]);
async function getActivites(cred){
const zeroLevel = Date.now();
fetch(`https://www.strava.com/api/v3/athlete/activities?access_token=${cred.access_token}`)
.then((res) => res.json())
.then((data) => {
for (const element of data) {
setActivities(oldActivities => [... oldActivities, element])
console.log(Date.now() - zeroLevel)
}
console.log('for-loop finished', Date.now() - zeroLevel)
})
.then(() => console.log(Date.now() - zeroLevel))
.then(() => console.log(activities))
}
I already tried to store the array in another object to make it more easily accessible, but I'm almost certain there's an easier way.

If data is an array, you don't need to iterate over it, you can just set the activites with data, instead of looping over it:
.then((data) => {
setActivities(data)
console.log('fetch finished', Date.now() - zeroLevel)
return data
})
.then((data) => {
data.map(activity => // do the fetch on each activity)
}
Or if you want to base the chained fetch on the state, then you can manually observe the change like this:
.then((data) => {
setActivities(data)
console.log('fetch finished', Date.now() - zeroLevel)
})
useEffect(() => {
activities.map(activity =>// do the fetch on each activity)
},[activities])

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.

Why data results showed twice while trying to load data from localStorage and API without using useEffect?

Here is part of my project where I have tried to load data from my localStorage and to load data from jsonplaceholder(this is for the sake of my enquiry, it is not part of my main project):
//load data from local storage and API without using useEffect
const savedCart = getDatabaseCart();
console.log(savedCart);
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((data) => console.log(data));
//load data from local storage and API using useEffect
useEffect(() => {
const savedCart = getDatabaseCart();
console.log(savedCart);
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((data) => console.log(data));
}, []);
and the results are here: the first result appears when I don't apply useEffect, the second result comes when I apply useEffect---->
why is this happening???
Most likely this is caused by a rerender (state updated somewhere else). The useEffect hook with the empty second parameter causes the handler to only run once. Here's more information: css-tricks.com/run-useeffect-only-once

Unable to access properties of object stored in a React state

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

Array becomes undefined whenever I try and access an element

Some Background
I am currently using React's context API to pass data collected from my API (Nodejs, Express, MongoDB) through components. There is an array named boards, in which I store the ID of various 'boards' the user is subscribed to, which will be later used to make GET or fetch requests to retrieve information about them.
When I first read the value in React:
fetch('http://localhost:8080/api/users/login', options)
.then(res => res.json())
.then( res => {
if(res.status !== 200) { setMessage(res.message) }
else {
setMessage("");
setLogged(true);
setuserID(res.userID);
setToken(res.token);
setBoards(res.boards); } //Retrieve boards here
console.log(res);
});
I later access the value of boards in a different component, where I need to use its elements to generate URLs for GET requests.
The Problem
When I log the value of boards:
useEffect(() => {
console.log(boards);
});
The value of the array in console
Though, when I try access it basically:
useEffect(() => {
console.log(boards[0]);
});
I get:
TypeError: boards is undefined
Any help on identifying the problem here would be appreciated.
Since the api needs to resolve you need to have check, you can try with below code
useEffect(() => { if(boards) console.log(boards[0]);}, [boards]);
Note:
useEffect(() => {
console.log(boards);
});
this will call every time, every render thats the reason even though the api takes time you were able to see.
You can read more about second argument of useEffect here

Set object in useEffect is not working in React

I am new in React.I just want to show records in the table and I fetch data like
const [allowances, setAllowances] = useState([]);
useEffect(() => {
fetch("http://127.0.0.1:8000/allowances/")
.then(data => {
return data.json();
})
.then(data => {
setAllowances(data);
})
.catch(err => {
console.log("error",err);
});
}, []);
Here how I check length=>
<div>{allowances.length}</div>
if i log the data in before setAllowances(data) ,data has 3 records.But when I check allowances.length, there are no records. its show like <div></div>. So I think this setAllowances is not working.right? what is wrong?
Update
This is my data of i logged before setAllowance=>
You are not setting the data correctly. As per the contents of data, it should be:
setAllowances(data.allowance);
For useEffect hooks to update every single time that your state changes, you need to pass it as a parameter. This happens by passing allowances within the square brackets after you set your callback to useEffect. Check out this link

Resources