React update values only once - reactjs

I have a newb question :)
I have a modal that opens in React Native with a dropdown select that requires values. I want to calculate the values whenever the modal opens.
let pickupTime; // Set a value that can be overwritten. I'm not using State because I want this value to change whenever I open the modal again.
const pickupTimeOptions = useRef([{ label: "", value: "" }]); // A ref to store the values
useEffect(() => {
const pickup_hours_today = business.pickup_hours_today; // Array of strings I pass to the modal.
console.log("pickup_hours_today", pickup_hours_today);
const options = pickup_hours_today.map((time) => {
return {
label: time,
value: time,
};
});
pickupTimeOptions.current = options;
}, [business.pickup_hours_today]);
console.log("pickupTimeOptions", pickupTimeOptions); // Let's see if we got it
The problem is that the ref never updates. The log prints this:
pickupTimeOptions Object {
"current": Array [
Object {
"label": "",
"value": "",
},
],
}
pickup_hours_today Array [
... // the string array of hours
]
Should be updating the ref
pickupTimeOptions Object {
"current": Array [
Object {
"label": "",
"value": "",
},
],
}
pickup_hours_today Array [
...
]
Should be updating the ref
What am I doing wrong? Should I handle this differently? I don't mind using state, but when I tried, it kept updating it whenever I selected a different values with the dropdown picker.

If you look at the order of console logs, it'll explain what's happening.
This is printed first, meaning calculation in useEffect hasn't happened yet
console.log("pickupTimeOptions", pickupTimeOptions); // Let's see if we got it
According to the documentation useEffect is only called after the render. You need to do the calculation before or during the render cycle.
You can use useMemo which is executed during rendering. Refer to the documentation for more details
Your updated code should look something like this
let pickupTime; // Set a value that can be overwritten. I'm not using State because I want this value to change whenever I open the modal again.
const pickupTimeOptions = useMemo(() => {
const pickup_hours_today = business.pickup_hours_today; // Array of strings I pass to the modal.
console.log("pickup_hours_today", pickup_hours_today);
const options = pickup_hours_today.map((time) => {
return {
label: time,
value: time,
};
});
return options;
}, [business.pickup_hours_today]);
console.log("pickupTimeOptions", pickupTimeOptions); // Let's see if we got it

Related

Adding an object to an array contained inside an object without overriding

I am currently trying to loop through an array of objects (each object is a task), where each task contains relevant information such as a name and date. Information from each task is then utilized in creating an object containing arrays, where each array contains objects that correspond to the date, or the array.
My current code is as follows:
contextTasks.forEach((taskItem) => {
taskItem["taskSchedule"].forEach((dateItem) => {
setItems((items) => ({
...items,
[dateItem["date"]]: [
{
name: taskItem["taskName"],
time: new Date(dateItem["time"]).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
}),
type: "Task",
},
],
}));
});
});
However, if there are multiple tasks with the same date, they will override each other and I only end up with one task per date. How would I go about pushing further objects to the array if there are other entries for that specific date?
Finished object:
Object {
"2021-04-21": Array [
Object {
"name": "Test Class v1",
"type": "Class",
},
],
"2021-04-24": Array [
Object {
"name": "Test Task v2",
"type": "Task",
},
//I would like to add another object here without overriding existing contents of the array
],
}
Have you tried using reduce ?
the idea will be to have something like this inside your accumulator:
{"date1": [{val}, {val}, ...] , "date2": [{val}, {val}, ...]}
array.reduce((acc, val) => {
// test if your accumulator has the same date as your date from the val
if(acc[val.date]) {
acc[val.date] = [... acc[val.date], ...your val]
} else {
// no date found in the accumulator so make acc.date = ...acc.date, val
acc[val.date] = [ val ]
}
}, {})
Sorry if the code is not perfect but if you want provide your initial array of data and I will fix the response code
The cause of your issue is the fact you're executing an async method inside a synchronous loop. Also, modifying state forces a re-render, and you're attempting to do it presumably many times at once. It might, and will cause a bottleneck at some point.
The solution: build your new state first, and execute a setState once.

Filter an Array through id and then mapping through a nested array inside

I'm stuck since a while trying to access a nested array inside another array after filtering it by an id. To be clear, this is the mock data I have:
bundleSets: [
{
id: 1,
title: "bundle set 1",
bundles: [
{
bundleTitle: "bundle 1",
content:[]
},
{
bundleTitle: "bundle 2",
content:[]
}
]
},
{ id:2,
title: "bundle set 2",
bundles: [
{bundleTitle: "ciaopao", content:[]}
]
},
{ id:3,
title: "bundle set 3",
bundles: [
{bundleTitle: "ciapo", content:[]}
]
}
]
Now I need to filter each bundleSets by id, and then access the bundles array inside and mapping those elements. This is what I tried to do:
const [key,setKey]=useState(1)
const [bundles,setBundles]=useState([])
useEffect(()=>{
const filteredBundles = bundleSets && bundleSets.filter(bundleSet=>bundleSet.id==key).map(filteredBundles=>{
return filteredBundles.bundles
})
setBundles(filteredBundles)
},[key])
Now If I try mapping the new state I can see on the console.log a weird [Array(1)] instead of the usual [{}] and I can't render it to the page. Where am I getting wrong with this?
Array.prototype.map returns an array and the callback you're passing to the map method returns filteredBundles.bundles which is an array. So, you get an array of arrays. filteredBundles is a confusing name, btw.
Since you're looking up the bundle by id and the ids are unique in the bundleSets array, you can use Array.prototype.find to find the bundle set by id and then get the bundle array. You can return an empty array if find returns undefined (if the key is not found).
const bundles = bundleSets?.find(set => set.id === key)?.bundles || []

Apollo cache.modify update slower than setState while using in React Beautiful Drag and Drop

I'm fetching a query, and modifying the order of the list in it, using cache.modify on the drag end.
This does modify cache, as it should but, it takes milliseconds to do that.
How to reproduce the issue:
I'm using react beautiful dnd, to make drag-n-drop card.
It provides onDragEnd handler, where we can specify what happens when the user stops dragging.
In this case, I want to reorder the list on the drag end.
Cache modify:
cache.modify({
id: cache.identify(data.pod),
fields: {
stories(existingStoriesRefs, { readField }) {
return reorder(existingStoriesRefs, sourceIndex, destinationIndex);
},
},
});
Reorder logic:
const reorder = (list: any[], startIndex: number, endIndex: number) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
This is correctly working and rendering using stories in setState.
But, instead of copying Apollo data, to a new state, I think it's better to directly modify the cache.
But, using cache.modify, it works but, rendering is kind of glitchy. It seems, it first renders the existing list and then, modify cache in the next render. The glitch is around less than a second, but visible to the user.
I fixed it using cache.modify inside, mutation, and using optimistic update.
moveStoryMutation({
variables: {
id: stories[sourceIndex].id,
sourceIndex,
destinationIndex,
},
optimisticResponse: {
__typename: "Mutation",
moveStory: true,
},
update: (proxy) => {
proxy.modify({
id: proxy.identify(pod),
fields: {
stories(existingStoryRefs) {
return reorder(
existingStoryRefs,
sourceIndex,
destinationIndex
);
},
},
});

Can't access nested stateful objects in ReactJS

I am trying to access a nested object in ReactJS. This is what the object looks like:
const characteristics = [
{ id: "geo", content: 'Geopolitical' },
{ id: "dog", content: 'Dog Loving' },
];
const starterColumns = {
"1": {
name: 'I am',
items: characteristics
},
"2": {
name: 'fullstack developer',
items: []
}
}
const [columns, setColumns] = useState(starterColumns);
This is the error I get when I try to console.log(columns['2']['items']['0']['id']):
TypeError: Cannot read property 'id' of undefined
Does this have to do with the fact that I am working with a stateful variable? Is something funky going on with the nested objects? Thanks for the help!
EDIT
The problem was that there was no object in the column so I had no object to access. Now the problem outstanding is how do I fill that void without displaying a new drag and drop piece. Thanks for helping!
EDIT
I used a try/catch statement to check the object so if it is empty, nothing happens.
Use try catch only for errors that you can't handle
To access an element use dot notation whenever it's possible instead of using bracket notation []
When there is an empty array in the items you can't access to the id you will get an error so the solution is to check if the array is not empty
columns['2'].items.length > 0
To access the first element of an array you have to use [0] instead of ['0']
try this solution
if (columns['2'].items.length > 0) {
console.log(columns['2'].items[0].id)
}

How add or delete an item from a tab in a hook?

I'm new with hooks and I would like to add or delete an item from my tab.
I don't know how to do it because this tab is an attribute of my hook tab.
const [questionResponses, setResponses] = useState(null);
I tried this fix but the syntax don't work :
setResponses( questionResponses[idQuestion].responses => [...questionResponses[idQuestion].responses,{
response_text: itemValue,
response_type: type,
}]);
I tried to use concat(), but it's freezing when the tab responses is not empty:
setResponses({
...questionResponses[idQuestion].responses, [idQuestion]: questionResponses[idQuestion]['responses'].concat([{
response_text: itemValue,
response_type: type,
}])
});
My tab have this structure:
[
{
"question_id": 1,
"question_text": "Best time of day",
"responses": [
{
"response_id": 33,
"response_text": "Morning",
"response_type": "radio"
}
]
},
{
"question_id": 2,
"question_text": "I heard about Marie-France Group via",
"responses": []
},...]
Could you help me please ? I don't know how to do it
Please see the code I added https://codesandbox.io/s/mystifying-liskov-quv5m.
Use spread operator for adding value in the existing array.
function addRespone() {
setResponses([...responses, { name: response }]);
}
For removal filter the exsiting array to remove the intended item.
function deleteResponse(itemIndex) {
const newResponses = responses.filter((item, index) => index !== itemIndex);
setResponses(newResponses);
}
Check codesandbox link for complete code.

Resources