Alternative or solution to update nested state - javascript

in my react app, there is a setting "page" with different setting categories with respective setting items.
Hence, I need a nested state (?) to avoid having lots of properties called "category1-id", "category1-element1-id" etc.
That looks as follows:
this.state = {
categories: [
{
id: 1,
label: "Category 1",
elements: [
{ id: 1, label: "Option 1", selected: true },
{ id: 2, label: "Option 2", selected: false },
],
},
{
id: 2,
label: "Category 2",
elements: [
{ id: 1, label: "Option 1", selected: true },
{ id: 2, label: "Option 2", selected: false },
{ id: 3, label: "Option 3", selected: false },
],
},
Other property: "...",
To update the state, I need a deep copy instead of a shallow one. How do I achieve this? Is there any other alternative solution?
Using
let categories = [...this.state.categories];
categories = JSON.parse(JSON.stringify(categories));
doesn't work (error) and is not really fail-proof.
Since this is the only nested state in my app, I'd like to avoid using lodash & co.
The function called when clicking on the option looks like this. But this modifies the state directly.
handleOptionSelect = (categoryId, option) => {
const categories = [...this.state.categories];
// FIND INDEX OF CATEGORY
const category = categories.find((category) => category.id === categoryId);
const indexCat = categories.indexOf(category);
// //FIND INDEX OF ELEMENT
const elements = [...category.elements];
const indexEle = elements.indexOf(element);
//MODIFY COPIED STATE
const e = categories[indexCat].elements[indexEle];
e.selected = e.selected ? false : true;
// //SET NEW STATE
this.setState({ categories });
}
Thank you very much for your help! I am still at the beginning trying to learn React and JS :)

What about if you use an object to store your categories? I mean, use a hash of key values in which each key is the id of the category.
this.state = {
categories: {
1: {
label: "Category 1",
elements: [
{ id: 1, label: "Option 1", selected: true },
{ id: 2, label: "Option 2", selected: false },
],
},
2: {...}
}
}
This should help you add or update any category, for example:
// Update category of id 4
this.setState({...categories, 4: updatedCategory })
Same principle should apply to the elements of the category if you use an object to store them.

Related

JSON: how do you add to a field that doesn't have a key?

I have a simple JSON object
simple_chart_config = {
chart: {
container: "#tree-simple"
},
nodeStructure: {
text: { name: "Question" },
children: [
{
text: { name: "Answer 1" }
}
]
}
};
And I'd like to add a new subfield within the first entry on the children array so that the final output is
simple_chart_config = {
chart: {
container: "#tree-simple"
},
nodeStructure: {
text: { name: "Question" },
children: [
{
text: { name: "Answer 1" },
children: [
{
text: { name: "Question 2" }
}
]
}
]
}
};
I've tried several methods, such as
var questionTwoStr = '{"children": [{"text": { "name": "Question 2" }}]}'
var questionTwo = JSON.parse(questionTwoStr);
simple_chart_config.nodeStructure.children[0] = questionTwo;
but I'm having issues working out all of the nested indexes in my head. This is for a tree in treant.js if that context is helpful at all.
I think I'm mostly confused because the place I'm trying to add the new subfield doesn't have a key, which I thought was required for JSON.
There's no need to use JSON here; you can add the object itself:
simple_chart_config.nodeStructure.children[0].children = [{text: { name:"Question 2" }}];

Typescript: Property 'name' does not exist on type 'Employee[]'

I am completely new to typescript, and I'm stumped by this error message: Property 'name' does not exist on type 'Employee[]' Could someone please point out where I'm not applying the "name" type within the Employee array? Thanks.
interface Employee {
id: number;
name: string;
title: string;
}
var employees: Employee[] = [
{ id: 0, name: "Franklin", title: "Software Enginner" },
{ id: 1, name: "Jamie", title: "Human Resources" },
{ id: 2, name: "Henry", title: "Application Designer" },
{ id: 3, name: "Lauren" title: "Software Enginner" },
{ id: 4, name: "Daniel" title: "Software Enginner 2" },
];
function fetchEmployeeName(id : number) {
var employee = employees.filter(
(employee) => employee.id === id
);
// The error occurs when I try to return a "name" under an employee that matched by id.
return employee.name;
}
console.log("Got Employee: "), fetchEmployeeName(3));
filter returns a new array containing all matching items:
[1, 2, 3].filter(i => i === 4)
The above will return an empty array.
What you want to use is find, which will return a single matching item or undefined.
Modify the fetchEmployeeName function to use find:
function fetchEmployeeName(id : number): string | null {
var employee = employees.find(
(employee) => employee.id === id
);
if (employee === undefined) return null;
return employee.name;
}
Try using find instead of filter. Filter returns an array. Find returns a single object. Next time, if using vscode, hover over employee on the first line of fetchEmployeeName, and check its type. Intellisense in vscode will point out to you that employee is clearly an array.
I highly recommend you to use find instead of filter, but if you really want to stick to your approach, you will have to access the only member in the employees array though its index (filter returns an array filled with the elements that meet the specified condition). E.G.:
return employee[0].name
Again, you can solve this particular issue by using filter, since it returns a single element you access without the need of an index (this will allow you to leave the return statement as it is).
there you have it, so what happened, your filter is returning to a new "Employee" that is not defined as an object,my advise is to always try to use pure functions and understand what your return is
interface Employee {
id: number;
name: string;
title: string;
}
var employees: Employee[] = [
{ id: 0, name: "Franklin", title: "Software Enginner" },
{ id: 1, name: "Jamie", title: "Human Resources" },
{ id: 2, name: "Henry", title: "Application Designer" },
{ id: 3, name: "Lauren", title: "Software Enginner" },
{ id: 4, name: "Daniel", title: "Software Enginner 2" },
];
function fetchEmployeeName (id:number, employees: Employee[]){
let employee = null
for (let i = 0, j = employees.length ; i < j; i++) {
if (employees[i].id === id) {
employee = employees[i].name
}
}
return employee
}
console.log(`Got employee 3: ${fetchEmployeeName(3,employees)}`);

Updating a specific object in an array

I'm writing an SPA in Svelte. Now, I'm fairly new to the concepts of ES6 so I'm having difficulties wrapping my head around some basic concepts.
I have a store:
import { writable } from "svelte/store";
function selectedOptions() {
const selection = writable([
{
id: 1,
title: "Title 1",
selections: []
},
{
id: 2,
title: "Title 2",
selections: []
},
{
id: 3,
title: "Title 3",
selections: []
},
{
id: 4,
title: "Title 4",
selections: []
},
{
id: 5,
title: "Title 5",
selections: []
},
{
id: 6,
title: "Title 6",
selections: []
}
]);
return {
subscribe: selection.subscribe,
updateSelection: item => {
selection.update((items) => {
//I want to update the object with the same id as the object
//I'm passing in to the method.
});
};
}
}
export default selectedOptions();
In my component, I want to pass an object and update the corresponding object in my array with provided values:
function handleChange(e) {
selectedOptions.updateSelection({
id: 1, title: "Title 1", selections: ["Option 1, Option 2"]
});
}
How do I "replace" an existing object with a new one thus triggering an update to all components that are subscribing to the store?
Use the spread syntax to copy all the original keys, then add the one you want to modify:
selection.update(items => {
return {
...items,
[item.id]: item
}
});
You could use the array method map and merge the new and old object if the id matches or just return the old object as-is if the id doesn't match.
updateSelection: item => {
selection.update(items => {
return items.map(i => (i.id === item.id ? { ...i, ...item } : i));
});
};

Is it possible to add custom attributes to panel items in a panelbar widget for Kendo?

I'm looking for a way to add and refer to custom attribute like an ID for an item in the the items array of my datasource using the PanelBar Widget for Kendo UI for JQuery.
I'm building my components in React.
Example:
componentDidMount(){
let itemsList = this.props.navProps.map((prop,index) => {
var open = false
if (index == 0) open = true
return { text: prop.name,
expanded: open,
items: [{text: "Sub Item 1", id: "hey"},
{text: "Sub Item 2", id: "ho"}]
}
})
const menuOptions = {
expandMode: "single",
dataSource: itemsList
}
let onSelect = function(e) {
console.log("Select: " + $(e.item).find("> .k-link").text());
}
let menu = new ppbar.ui.PanelBar(menudiv,menuOptions);
$(menu.element).kendoPanelBar({
select: onSelect,
});
render(){
return (
<div id='menudiv' />
)
}
For some reason no matter what I try the only thing I can find in the $(e.item) is the item's text value, but not the id value.
You can use a template to add some html with the ID attribute to each item in the panelbar.
var inlineDefault = new kendo.data.HierarchicalDataSource({
data: [
{
id: 1, text: "Furniture", items: [
{ id: 2, text: "Tables & Chairs" },
{ id: 3, text: "Sofas" },
{ id: 4, text: "Occasional Furniture" }
]
},
{
id: 5, text: "Decor", items: [
{ id: 6, text: "Bed Linen" },
{ id: 7, text: "Curtains & Blinds" },
{ id: 8, text: "Carpets" }
]
}
]
});
$("#panelbar-left").kendoPanelBar({
dataSource: inlineDefault,
template: "<span class='myItemClass' data-itemid='#= item.id #'>#= item.text #</span>",
select: function(e){
alert("Item id = " + $(e.item).find(".myItemClass").data('itemid'));
}
});
DEMO
In this example, I have surrounded the text with a span. The class myItemClass just lets me easily select the node and data-itemid is the id from the data. Then in the onSelect you can retrieve the id via $(e.item).find(".myItemClass").data('itemid').

Ramda.js Combine two array of objects that share the same property ID

I have these two array of objects
todos: [
{
id: 1,
name: 'customerReport',
label: 'Report send to customer'
},
{
id: 2,
name: 'handover',
label: 'Handover (in CRM)'
},
]
And:
todosMoreDetails: [
{
id: 1,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: []
},
{
id: 2,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: []
}
]
So that the final array of objects will be a combination of the two, based on the object ID, like below:
FinalTodos: [
{
id: 1,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: [],
name: 'customerReport',
label: 'Report send to customer'
},
{
id: 2,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: [],
name: 'handover',
label: 'Handover (in CRM)'
}
]
I tried with merge mergeAll and mergeWithKey but I am probably missing something
You can achieve this with an intermediate groupBy:
Transform the todosMoreDetails array into an object keyed by todo property ID using groupBy:
var moreDetailsById = R.groupBy(R.prop('id'), todosMoreDetails);
moreDetailsById is an object where the key is id, and the value is an array of todos. If the id is unique, this will be a singleton array:
{
1: [{
id: 1,
checked: false,
link: {
type: 'url',
content: 'http://something.com'
},
notes: []
}]
}
Now transform the todos array by merging each todo to it's details you retrieve from the grouped view:
var finalTodos = R.map(todo => R.merge(todo, moreDetailsById[todo.id][0]), todos);
An alternate more detailed way:
function mergeTodo(todo) {
var details = moreDetailsById[todo.id][0]; // this is not null safe
var finalTodo = R.merge(todo, details);
return finalTodo;
}
var moreDetailsById = R.groupBy(R.prop('id'), todosMoreDetails);
var finalTodos = todos.map(mergeTodo);
I guess merge is only used for arrays. Have a search for object "extend". Maybe storing the todo details not in seperate objects is the better solution.
Using jQuery? https://api.jquery.com/jquery.extend/
Using underscore? http://underscorejs.org/#extend
Native approach? https://gomakethings.com/vanilla-javascript-version-of-jquery-extend/
Using underscore:
var result = [];
var entry = {};
_.each(todos, function(todo) {
_.each(todosMoreDetails, function(detail) {
if (todo.id == detail.id) {
entry = _.extend(todo, detail);
result.push(entry);
}
}
});
return result;

Categories