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
Related
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])
I am newbie in React Native and I am trying to store and get an array with AsyncStorage in ReactNative.
I have two problems.
First, I do not know why but when I storage data, it only works the second time but I am calling first the set of useState.
const handleAddTask = () => {
Keyboard.dismiss();
setTaskItems([...taskItems, task]);
storeData(taskItems);
};
Second, how can I call the getData function to get all the data and show it? Are there something like .onInit, .onInitialize... in ReactNative? Here is my full code
const [task, setTask] = useState();
const [taskItems, setTaskItems] = useState([]);
const handleAddTask = () => {
Keyboard.dismiss();
setTaskItems([...taskItems, task]);
storeData(taskItems);
};
const completeTask = (index) => {
var itemsCopy = [...taskItems];
itemsCopy.splice(index, 1);
setTaskItems(itemsCopy);
storeData(taskItems);
}
const storeData = async (value) => {
try {
await AsyncStorage.setItem('#tasks', JSON.stringify(value))
console.log('store', JSON.stringify(taskItems));
} catch (e) {
console.log('error');
}
}
const getData = async () => {
try {
const value = await AsyncStorage.getItem('#tasks')
if(value !== null) {
console.log('get', JSON.parse(value));
}
} catch(e) {
console.log('error get');
}
}
Updating state in React is not super intuitive. It's not asynchronous, and can't be awaited. However, it's not done immediately, either - it gets put into a queue which React optimizes according to its own spec.
That's why BYIRINGIRO Emmanuel's answer is correct, and is the easiest way to work with state inside functions. If you have a state update you need to pass to more than one place, set it to a variable inside your function, and use that.
If you need to react to state updates inside your component, use the useEffect hook, and add the state variable to its dependency array. The function in your useEffect will then run whenever the state variable changes.
Even if you're update state setTaskItems([...taskItems, task]) before save new data in local storage, storeData(taskItems) executed before state updated and save old state data.
Refactor handleAddTask as below.
const handleAddTask = () => {
Keyboard.dismiss();
const newTaskItems = [...taskItems, task]
setTaskItems(newTaskItems);
storeData(newTaskItems);
};
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
Currently I'm building a pusher chat app with react. I'm trying to keep a list of online users. I'm using this code below:
const [users, setUsers] = useState([]);
useEffect(() => { // UseEffect so only called at first time render
window.Echo.join("server.0")
.here((allUsers) => {
let addUsers = [];
allUsers.map((u) => {
addUsers.push(u.name)
})
setUsers(addUsers);
})
.joining((user) => {
console.log(`User ${user.name} joined`);
setUsers([users, user]);
})
.leaving((user) => {
console.log(`User ${user.name} left`);
let addUsers = users;
addUsers.filter((u) => {
return u !== user.name;
})
setUsers(addUsers);
})}, []);
Whenever I subscribe to the pusher channel, I receive the users that are currently subscribed and the state is set correctly. All subscribed users are showing. However when a new user joins/leaves, the .joining/.leaving method is called and the users state is empty when I console log it. This way the users state is being set to only the newly added user and all other users are being ignored. I'm new to react so there is probably a simple explanation for this. I was not able to find the answer myself tough. I would really appreciate your help.
I saw the problem in joining. You need to update setState like this: setUsers([...users, user.name]);
And leaving also need to update:
const addUsers = users.filter((u) => {
return u !== user.name;
});
setUsers(addUsers);
here should also rewrite:
let addUsers = allUsers.map((u) => u.name);
setUsers(addUsers);
I found the issue. The problem is that when accessing state from within a callback funtion, it always returns the initial value. In my case an empty array. It does work when using a reference variable. I added the following lines:
const [users, _setUsers] = useState([]);
const usersRef = React.useRef(users);
const setUsers = data => {
usersRef.current = data;
_setUsers(data);
}
Each time I update the users state, I use the setUsers function. Now when I acces the state from inside my callback function with usersRef.current I get the latest state.
Also I used the code from the answer of #Viet to update the values correctly.
I have no issues fetching the data from an API using useEffect. That works fine.
The problem is that I need to apply some processing to the data before I actually render it out (in this case, I need to shuffle the array that I receive).
I tried a million different ways, but I just can't find the right place to write that logic. Basically, it won't work anywhere.
What is the right way of going about this?
you can do everything with data before setState.
is useEffect when you fetched data from Api, shuffle it and then do setState.
little example:
useEffect(() => {
axios.get("http://example.com/data").then(response => {
const data = shuffle(response.data);
setState(data);
})
});
useEffect(() => {
const fetchData = async () => {
await axios.get("http://example.com/data").then(response => {
const data = shuffle(response.data);
setState(data);
});
};
fetchData();
return () => {
// Clean up func
}
}, []); //[] will prevent infinite API calling.