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

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.

Related

Inserting the value of objects in an object into an array (map takes too long) so I can pass the array to Dropdown (React Native - Expo)

It needs to pass values (name) from the object that is in the object to the array.
I'm doing it now using maps, but since there are a lot of objects, it takes half a minute.
Array of objects that I get using the API:
https://pastebin.com/SPpbzwn3
[
{
"name": "A",
"schoolCount": 2
},
{
"name": "B",
"schoolCount": 1
},
{
"name": "C",
"schoolCount": 1
}]
What I need to get:
["A","B","C"]
How do I now get values from an object into an array:
.then(function (response) {
response.data.map(({ name }) => {
setCity((city) => [...city, name]);
});
})
Depends. Is:
"setCity" created by useState?? if yes each interation of map is causing render so I am not suprised that it takes time.
I would rather set entire array once it is mapped into object by e.g. setCities to speed it up.

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 || []

React update values only once

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

More than one getItem localStorage in a state

Is it possible to have more than one localStorage.getItem in state?
Right now I have this:
const [list, useList] = useState(
JSON.parse(localStorage.getItem("dictionary")) || [] //tasks in my to-do
);
and I should also keep in this state my subtasks, contained in a task, with this structure:
- task {
- id
- body
- subtasks
[{
- id
- body
}]
}
Can I save also the subtasks in local storage and access them with getItem?
These are what I want to use to get my subtasks:
JSON.parse(localStorage.getItem("domain")) || []
JSON.parse(localStorage.getItem("range")) || []
Yes, you can have more than one array of values in local storage. You need to set the item before you can access it though, you should also serialize the object or array to a string when saving it.
localStorage.setItem("dictionary", JSON.stringify([]));
localStorage.setItem("domain", JSON.stringify([]));
localStorage.setItem("range", JSON.stringify([]));
alert(JSON.parse(localStorage.getItem("dictionary")));
alert(JSON.parse(localStorage.getItem("domain")));
alert(JSON.parse(localStorage.getItem("range")));
Lucky me, I saw your other question which contains a running code snippet, you should add it here too!
From what I saw you're trying to create a tree of tasks, dictionary is a task and it can have subtasks such as domain and range, right? Then you should have a data structure like this:
singleTask = {
id: 0,
body: "task",
domain: [
{
id: 00,
body: "subtask domain 1"
},
{
id: 01,
body: "subtask domain 2"
}
],
range: [
{
id: 10,
body: "subtask range 1"
},
{
id: 11,
body: "subtask range 2"
}
]
}
When you're rendering a task as TaskListItem, you render the task.body. Then pass task.domain to a SubtaskDomain component, task.range to a SubtaskRange component.
When you submit a subtask, update the main list in App, after you do that, update local storage, you already do that, but you actually only need one set item, and it's
localStorage.setItem("dictionary", JSON.stringify(listState));
because you have everything in it!

I try to implement a connection using relay and all the node's IDs are the same

I write a really simple schema using graphql, but some how all the IDs in the edges are the same.
{
"data": {
"imageList": {
"id": "SW1hZ2VMaXN0Og==",
"images": {
"edges": [
{
"node": {
"id": "SW1hZ2U6",
"url": "1.jpg"
}
},
{
"node": {
"id": "SW1hZ2U6",
"url": "2.jpg"
}
},
{
"node": {
"id": "SW1hZ2U6",
"url": "3.jpg"
}
}
]
}
}
}
}
I posted the specific detail on github here's the link.
So, globalIdField expects your object to have a field named 'id'. It then takes the string you pass to globalIdField and adds a ':' and your object's id to create its globally unique id.
If you object doesn't have a field called exactly 'id', then it wont append it, and all your globalIdField will just be the string you pass in and ':'. So they wont be unique, they will all be the same.
There is a second parameter you can pass to globalIdField which is a function that gets your object and returns an id for globalIdField to use. So lets say your object's id field is actually called '_id' (thanks Mongo!). You would call globalIdField like so:
id: globalIdField('Image', image => image._id)
There you go. Unique IDs for Relay to enjoy.
Here is the link to the relevant source-code in graphql-relay-js: https://github.com/graphql/graphql-relay-js/blob/master/src/node/node.js#L110
paste the following code in browser console
atob('SW1hZ2U6')
you will find that the value of id is "Image:".
it means all id property of records fetched by (new MyImages()).getAll()
is null.
return union ids or I suggest you define images as GraphQLList
var ImageListType = new GraphQL.GraphQLObjectType({
name: 'ImageList',
description: 'A list of images',
fields: () => ({
id: Relay.globalIdField('ImageList'),
images: {
type: new GraphQLList(ImageType),
description: 'A collection of images',
args: Relay.connectionArgs,
resolve: (_, args) => Relay.connectionFromPromisedArray(
(new MyImages()).getAll(),
args
),
},
}),
interfaces: [nodeDefinition.nodeInterface],
});

Resources