Array isn't filled with object in Javascript - reactjs

So after the content is loaded in the website I'm trying to call a function to fill [fields] (State) with the events but something Isn't working.
Here is what I mean:
const [events, setEvents] = useState([]);
useEffect(() => {
if (orders.orders !== null && orders.isLoading !== true)
orders.orders.map((order) =>
setEvents([
...events,
{
title: `Meeting with ${order.customer.firstName}`,
date: `${order.appointment}`,
},
])
);
setLoading(false);
}, [orders]);
So when I console log the events, I get [] (empty), but if i console.log the object it works.

I'm not sure what you are trying to achieve with the code but looks like your useEffect has a missing dependency of events. Since you are also updating events with setEvents, you should update with
setEvents(prev=>[
...prev,
{
title: `Meeting with ${order.customer.firstName}`,
date: `${order.appointment}`,
}
])
to avoid an infinite loop.

You are directly updating state so it is shown in console but in react rerender should be done to update state so pass parameter for setEvents and spread parameter but not directly events because you cannot directly update state.
const [events, setEvents] = useState([]);
useEffect(() => {
if (orders.orders !== null && orders.isLoading !== true)
orders.orders.map((order) =>
setEvents(prev => [
...prev,
{
title: `Meeting with ${order.customer.firstName}`,
date: `${order.appointment}`,
},
])
);
setLoading(false);
}, [orders]);

Related

useState not updating rendered values after getting document snapshots

I am having a problem assigning data to useState by fetching data using reference type value from firebase.
const [preOil, setPreOil] = useState([]);
const [oilChange, setOilChange] = useState([]);
useEffect(() => {
getDocs(query(collection(db, "oil"), orderBy("timestamp"))).then(
(snapshot) => {
setPreOil(
snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}))
);
}
);
}, []);
useEffect(() => {
let current = preOil.length > 0 ? [...preOil] : [];
current.map((_oil, i) => {
getDoc(_oil.oilReference).then((oilRef) => {
current[i].oilReference = oilRef.data();
});
});
setOilChange(current);
}, [preOil]);
In the first useEffect, the data is fetched successfully in this form,
preOil = {
id:"RxbAOAIs8d3kGOHNskJ4",
oilReference: Ta {converter: null, _key: ut, type: 'document', firestore: Na},
customerName: "USAMA",
}
In the second useEffect based on [preOil], I need to reassign the oilReference with the data fetched from firestorm through its reference(oilReference), The data is successfully fetched from the database and assigned to current but The main problem is when I set to state setOilChange(current) it updates my oilChange state when I inspect in inspect tools in chrome but in JSX the changes don't reflect
I am updating the state in useEffect
I am having desired data assigned in a local variable and assign that variable to the state
Then What am I missing?
In your second useEffect(), more specifically, in
current.map((_oil, i) => {
getDoc(_oil.oilReference).then((oilRef) => {
current[i].oilReference = oilRef.data();
});
});
setOilChange(current);
You are mutating the content of the current variable. This mutation, because it is async, will happen after the setOilChange call. Such mutation will thus not trigger a re-render.
What you need is to instead first wait for all the docs to be loaded and only after that set the state. Example:
const _docs = current.map((_oil, i) => {
return getDoc(_oil.oilReference).then((oilRef) => { // changed here
return { // changed here
...current[i], // changed here
oilReference: oilRef.data() // changed here
} // changed here
}); // changed here
});
Promise.all(_docs).then(() => {
setOilChange(_docs);
});
Also notice I didn't mutate current, rather I returned a new object. This is not mandatory but just a best practice overall.

How to synchronous useState with passing state to localstorage

I ran into an asynchronous useState problem.
I have a situation where I first need to add an object to the state array in the handler. And then add this state to the localStorage.
setFavoritedSongs ((prev) => [...prev, {name: "Lala", length: "3:20"}]);
localStorage.setItem("storageItemName", JSON.stringify(favoritedSongs));
If I delete the entire localStorage first and run the handler. So an empty array is added to my localStorage (the state shows me updated). After the action again, the required object is finally added to my array.
I tried something like this, but still the same problem.
const tempArray = favoritedSongs.push({ name: "Lala", length: "3:20" });
localStorage.setItem(storageItemName, JSON.stringify(tempArray));
How do you solve this, please?
/// EDIT
I have something like this
const FavoriteSong = () => {
const song = { id: 1, name: "Lala", length: "3:20" };
const [favoritedSongs, setFavoritedSongs] = useState([]);
const [isFavorited, setIsFavorited] = useState(false);
useEffect(() => {
if (localStorage.getItem("storageItemName")) {
const storageSongs = JSON.parse(
localStorage.getItem("storageItemName") || ""
);
setFavoritedSongs(storageSongs);
const foundSong = storageSongs?.find((song) => song.id === song.id);
foundSong ? setIsFavorited(true) : setIsFavorited(false);
}
}, [song]);
const handleClick = () => {
if (isFavorited) {
const filteredSong = favoritedSongs.filter(
(song) => song.id !== song.id
);
localStorage.setItem("storageItemName", JSON.stringify(filteredSong));
setIsFavorited(false);
} else {
setFavoritedSongs((prev) => [...prev, song]);
localStorage.setItem("storageItemName", JSON.stringify(favoritedSongs));
setIsFavorited(true);
}
};
return <div onClick={handleClick}>CLICK</div>;
};
export default FavoriteSong;
Just place your localStorage.set logic inside a useEffect to make sure it runs after the state actually changes.
useEffect() => {
localStorage.setItem(...);
}, [favoritedSongs]};
For that you can Use the condition If data in the array then It will set in localStorage otherwise not
const tempArray = favoritedSongs.push({ name: "Lala", length: "3:20" });
tempArray.length && localStorage.setItem(storageItemName, JSON.stringify(tempArray));
.
setFavoritedSongs ((prev) => [...prev, {name: "Lala", length: "3:20"}]);
FavoritedSongs.length(your state name) && localStorage.setItem("storageItemName", JSON.stringify(favoritedSongs));

Recoil refresh state not working of that state has been altered

I'm trying to reset my state by calling the APi to get the latest data. The API call returns an array of object.
export const launchesState = atom<Launch[]>({
key: 'launchesStateLatest',
default: selector<Launch[]>({
key: 'launchesStateInit',
get: async () => {
const response = await getLaunches();
return response.data.map(launch => ({ ...launch, isSelected: false }));
},
}),
});
I'm using a selectorFamily to select each object when the list is rendered.
export const launchState = selectorFamily<Launch | undefined, string>({
key: 'launchState',
get:
missionName =>
({ get }) => {
return get(launchesState).find(item => item.mission_name === missionName);
},
set:
missionName =>
({ get, set }) => {
const currentState = get(launchesState);
const index = currentState.findIndex(item => item.mission_name === missionName);
set(
launchesState,
produce(currentState, draft => {
draft[index].isSelected = !currentState[index].isSelected;
}),
);
},
});
The UI contains a checkbox for each item in the array and when click it uses the set function in the selectorFamily ti update the launchesState.
I'd like to refresh the data using:
useRecoilRefresher_UNSTABLE(launchesState)
Which works ok if the data has never been altered, or is reset using useResetRecoilState(launchesState), but if I have clicked any of the checkboxes and altered the state then the state isn't refreshed.
Can someone help me understand why this is happening, is it a bug or is this happening for a reason?

How can I change the array key value before passing to the state in react?

Question, I have this state coming from the backend It's a array of messages that will be store in state using useState. This state will be pass on in the child component. The problem is I want to change value of a specific key before storing it into the state.
Sample
Messages Array sample data
const messages = [
{
value: 'sample value one',
status: false,
},
{
value: 'sample value two',
status: false,
},
];
UseSelector
const messageGetById = useSelector((state) => state.messageGetById);
const { message } = messageGetById;
UseEffect
useEffect(() => {
if (message) {
setCurrentMessage(message);
}
}, [message]);
The output that I want is before passing the message into setCurrentMessage, all the value of status will be change to true.
Thanks!
You can use map method to map thought the array and change the status to true.
useEffect(() => {
if (message) {
const newMessages = messages?.map((mess) => {
return {...mess, status: true}})
setCurrentMessage(newMessages);
}}, [message]);
Set the mapped state with useEffect
useEffect(() => {
const data = [...message];
if (data.length > 0) {
data.map((ele) => ({
value: "YOUR CHANGED VALUE",
status: ele.status,
}));
setCurrentMessage(data);
}
}, [message]);

How to get rid of endless requests?

please help with useCallback, how to call it so that useEffect does not send endless requests to the server? Now it sends endless requests, I know that useCallback is needed for this, but I don’t understand how to use it
// Get data
useEffect(() => {
api.getTestLeagues()
.then(data => setLeagues(data));
api.getTestCountries()
.then(data => setCountries(data));
if(sidebars === null && leagues !== null && countries !== null) {
onTest()
}
}, [api, leagues, sidebars, countries, onTest]);
// The 'onTest' function makes the dependencies of useEffect Hook (at line 29) change on every render. Move it inside the useEffect callback. Alternatively, wrap the 'onTest' definition into its own useCallback() Hook
// test
const onTest = () => {
const updateObj = {
leagues: {
title: "Мои лиги",
items: leagues
},
countries: {
title: "Страны",
items: countries
}
};
setSidebars(updateObj);
};
Req:
I'm assuming you want to load data once, normally done with componentDidMount.
2 useEffect fired only once, running in parralel
useEffect(() => {
api.getTestLeagues()
.then(data => setSidebarItemsLeagues(data)); // for items 1 (not null)
}, []);
useEffect(() => {
api.getTestCountries()
.then(data => setSidebarItemsCountries(data)); // for items 2 (not null)
}, [])
useEffect running only on data changes
useEffect(() => {
if(sidebars === null && leagues !== null && countries !== null) {
const updateObj = {
leagues: {
title: "Мои лиги",
items: leagues
},
countries: {
title: "Страны",
items: countries
}
};
setSidebars(updateObj);
}
}, [leagues, countries]);
Update
api defined earlier ...
const api = new Services();
of course changes on every render but should not be taken as dependency as it's always uses the same urls - they are not parametrized. No meaningful (affecting data) changes - don't care.
If api is not passed to rendered components (ref cange could force rerenderings) then there is no need to take care about it.
You can get rid of this definition by
useEffect(() => {
new Service().getTestLeagues()
.then(data => setSidebarItemsLeagues(data)); // for items 1 (not null)
}, []);
useEffect(() => {
new Service().getTestCountries()
.then(data => setSidebarItemsCountries(data)); // for items 2 (not null)
}, [])
Even if it looks as unnecessary duplicating instances it can be more optimal when component is frequently rerendered - like in open-close view changes.
You're in an infinite loop because your effect depends on values you change in it: each time you fetch some data from the api you change leagues and countries states which leads to running the effect again.
Split the effects so that the requests effect does not depend on the values it changes:
useEffect(() => {
api.getTestLeagues().then(data => setLeagues(data));
api.getTestCountries().then(data => setCountries(data));
}, [api]);
useEffect(() => {
if(sidebars === null && leagues !== null && countries !== null) {
onTest();
}
}, [leagues, sidebars, countries, onTest]);
Also:
Define api outside of your component (above) so the value stays stable.
You can wrap onTest in a useCallback before your effects, like #Ramesh suggested.
Try wrapping onTest with useCallback like this:
const onTest = useCallback(() => {
const updateObj = {
leagues: {
title: "Мои лиги",
items: leagues
},
countries: {
title: "Страны",
items: countries
}
};
setSidebars(updateObj);
},[]);
The second argument to useCallback is an array of dependencies similar to useEffect.
BTW this will memorize the function so that a new function doesn't get created on every render.
update
It is better to split the useEffect so that each useEffect just focuses on executing something when their dependencies are changed.
useEffect(() => {
api.getTestLeagues()
.then(data => setLeagues(data));
api.getTestCountries()
.then(data => setCountries(data));
}, [api]);
useEffect(() => {
if(sidebars === null && leagues !== null && countries !== null) {
onTest()
}
},[onTest, sidebars, leagues, countries])
When you send a request that changes the state and also use the same state as a dependency that'll again send a request(because the value of the state changes) it'll cause an infinite loop.
update 2
The issue might be with the api class instance because on every new render a new instance is created.
You said you were doing something like this const api = new Services(); so wrap that with useMemo like this:
const api = useMemo(() => {
return new Services();
})

Resources