How to call api inside of a loop and perform action on it inside of useEffect React Native - reactjs

Here is my scenario:
I'm having a cart object in Redux store having information in the form of array of objects having sellerId and the array of products, and I want to map on each object to get sellerId and then fetch seller's data from API on page load.
Here's my code
const [uniqueSellers, setUniqueSellers] = useState([]);
useEffect(() => {
const uniqueSellerIds = [];
cart.filter((item) => {
if (!uniqueSellerIds.includes(item.sellerId)) {
uniqueSellerIds.push(item.sellerId);
}
});
if (uniqueSellerIds.length === 1) setItems(["Seller's delivery"]);
uniqueSellerIds.map((sellerId) =>
axios.get(`${devBaseURL}/sellers/${sellerId}`).then((res) => {
setUniqueSellers((prev) => [
...prev,
{
sellerId: res.data.data[0]._id,
sellerProvince: res.data.data[0].businessAddress.province,
},
]);
}),
);
// Here I want to perform some operations on uniqueSellers state, but it's not available here
console.log('uniqueSellers: ', uniqueSellers); // logs empty array
setLoading(false);
return () => {
setUniqueSellers([]);
};
}, []);

Mutating state is an async process. Fetch operations are also async. So, your console log always executes before your axios call and setUniqueSellers hook.
Listen changes in uniqueSellers array inside another useEffect by giving it as a dependency.
useEffect(() => {
console.log(uniqueSellers); //will log after every change in uniqueSellers
}, [uniqueSellers])

Related

How to get updated data in a setTimeout from redux saga?

I am using Redux Saga along with Redux Toolkit. I have dispatched an action and need its result in a setTimeout. But once I have dispatched the action the code execution continues and when the setTimeout runs, I do not get the updated result instead I get the results from previously dispatched action (I dispatch the same action when the component mounts).
File.js
const {neededData} = useSelector(state => state.something) // property from redux store
useEffect(() => {
dispatch(Slice.actions.getHealthDataBetweenDates(params));
}, [])
function someFunction() {
dispatch(Slice.actions.getHealthDataBetweenDates(params));
console.log(neededData) // logs an array of objects, but the it is not updated data
setTimeout(() => {
console.log(neededData) // logs an array of objects, but it is not updated data
}, 1000)
}
Saga.js
function* getHealthDataBetweenDates({ payload }) {
try {
const result = yield call(ProfileApi.getHealthDataBetweenDates, payload)
if (result.status == true) {
yield put(Slice.actions.getHealthDataBetweenDatesSuccess(result.data));
console.log('saga minutes', result.data.data) // returns array of object, with updated data
}else {
yield put(Slice.actions.getHealthDataBetweenDatesFailure());
}
} catch (error) {
console.log('error: ' + error)
}
}
When the function executes, I first get the log right below the dispatch statement, then the log from saga and then the log from setTimeout.
When you run setTimeout, you create a closure around the function you pass in. Variables are "frozen" at the time the closure is created. React will still re-render your component when your saga updates your store, but the values in setTimeout have already been closed. The MDN spec on closures is well-written and very helpful.
React offers an "escape hatch" from these restrictions: refs. Refs exist outside the render cycle of the component and its closures. You can wire a ref to your store with useEffect to get the behavior you're looking for.
Here's a minimal example:
// let's say the selector returns 0
const data = useSelector(state => state.data);
const ref = useRef(data);
// update the ref every time your selector changes
useEffect(() => {
ref.current = data;
}, [data]);
// start timeouts
useEffect(() => {
// set data at 500ms
setTimeout(() => dispatch(Slice.actions.setData(1)), 500);
// log data at 1000ms
setTimeout(() => console.log({ data: ref.current }), 1000);
}, []);
This will log { data: 1 } to the console after ~1000ms.

How to get session data in next-auth after loading is done

I am trying to get data using useSession, and this data I store in my state, but when I get data using this, it returns me null object since data is still in loading state.
Is there any way I can get data only after status is not loading and till then block the page?
const { data: session, status } = useSession();
useEffect(() => {
const { data } = getCookieData(session);
if (data) setUser(() => data.user);
}, []);
Comment turned into an answer:
useSession changes the state after the status changes. If you want the code inside the useEffect to run after state changes, you probably want to put that state inside the brackets, so this code:
useEffect(() => {
const { data } = getCookieData(session);
if (data) setUser(() => data.user);
}, []);
Would become this
useEffect(() => {
const { data } = getCookieData(session);
if (data) setUser(() => data.user);
}, [data,status]);
And in general whenever you need to trigger some function every time a particular prop or state changes you should place those variables inside the useEffect()
More info about useEffect and lifecycles in the docs:
https://reactjs.org/docs/hooks-effect.html

ReactJS array from usestate is still empty after setTimeout

please I'm solving one problem (just learning purposes). I'm using useState() hook and then, after some timeout I want add next items into array from remote fetch.
My code snippet look like:
const [tasks, setTasks] = useState([]);
const url = 'https://pokeapi.co/api/v2/pokemon?offset=0&limit=5';
// asnynchronous call. Yes, it's possible to use axios as well
const fetchData = async () => {
try {
let tasksArray = [];
await fetch(url)
.then((response) => response.json())
.then((data) => {
data.results.map((task, index) => {
// first letter uppercase
const taskName = task.name.charAt(0).toUpperCase() + task.name.slice(1);
tasksArray.push({ id: index, name: taskName });
});
});
console.log('Added tasks:' + tasks.length);
setTasks(_.isEmpty(tasks) ? tasksArray : [...tasks, tasksArray]);
} catch (error) {
console.log('error', error);
}
};
useEffect(() => {
// Add additional example tasks from api after 5 seconds with
// fetch fetchData promise
setTimeout(fetchData, 5000);
}, []);
Code works fine with useEffect() hook. But in async function my array is empty when I add some tasks within five seconds and it will be replaced by fetched data and one empty
I added Butter and Milk within 5 seconds to my Shopping list
But after timeout my tasks array will be replaced by remote fetched data.
And as you can see, tasks array lenght is 0 (like console.log() says)
Please, can you exmplain me, why my tasks array is empty if there exists 2 items before 5 seconds.
Of course, I'm adding my tasks to the list normally after hit Enter and handleSubmit
const handleSubmit = (e) => {
e.preventDefault();
//creating new task
setTasks([
{
id: [...tasks].length === 0 ? 0 : Math.max(...tasks.map((task) => task.id)) + 1,
name: newTask,
isFinished: false,
},
...tasks,
]);
setNewTask('');
}
Thanks for help or explain. It the problem that useEffect is called after rendering? Of this causing async behavior?
I could not understand your code fully correctly but my guess is
the fetchData function you have declared might refer to the tasks at the time of declaration.
so every time you call fetchData you might not see the changed tasks state...
if this is the case try using useCallback with the dependency tasks...
what useCallback does is it stores the function in memory and if smth in dependency changes the function's logic changes to dependencies you declared.
If you have used eslint, calling a function inside useEffect will give you error similar to below
The ‘fetchOrg’ function makes the dependencies of useEffect Hook (at line 6) change on every render. Move it inside the useEffect callback. Alternatively, wrap the ‘fetchOrg’ definition into its own useCallback() Hook
Your code is confusing. You can place everything inside an useEffect, and I believe the thing you are trying to achieve is a long poll query. (for that you use setInterval() and you have to clear the function in useEffect
my solution for you:
const [tasks, setTasks] = useState([]);
const url = 'https://pokeapi.co/api/v2/pokemon?offset=0&limit=5';
const request = {method: GET, Headers: {"Content-type":"application/json"}}
useEffect(() => {
function fetchData(){
fetch(url, request).then(res => res.json().then(data => {
data.results.map((task, index) => {
const taskName = task.name.charAt(0).toUpperCase() + task.name.slice(1);
setTasks((prevState) => ([...prevState,{ id: index, name: taskName }]));
});
})
}
const interval = setInterval(() => {
fetchData();
}, 5000)
return () => clearInterval(interval)
}, []);
please do not forget two things:
This approach is only good when you have 5 to 10 simultaneous clients connected since it is not performance effective on the backend, I would recommend instead an alternative based on realtime communication (with the listening for new events.
please do not forget to specify the {method, headers, body) in the fetch function

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

Using useEffect properly when making reqs to a server

I have a handleRating function which sets some state as so:
const handleRating = (value) => {
setCompanyClone({
...companyClone,
prevRating: [...companyClone.prevRating, { user, rating: value }]
});
setTimeout(() => {
handleClickOpen();
}, 600);
};
I think also have a function which patches a server with the new companyClone values as such:
const updateServer = async () => {
const res = await axios.put(
`http://localhost:3000/companies/${companyClone.id}`,
companyClone
);
console.log("RES", res.data);
};
my updateServer function gets called in a useEffect. But I only want the function to run after the state has been updated. I am seeing my res.data console.log when I load my page. Which i dont want to be making reqs to my server until the comapanyClone.prevRating array updates.
my useEffect :
useEffect(() => {
updateServer();
}, [companyClone.prevRating]);
how can I not run this function on pageload. but only when companyClone.prevRating updates?
For preventing function call on first render, you can use useRef hook, which persists data through rerender.
Note: useEffect does not provide the leverage to check the current updated data with the previous data like didComponentMount do, so used this way
Here is the code example.
https://codesandbox.io/s/strange-matan-k5i3c?file=/src/App.js

Resources