Adding new item to Object useState - javascript

I need help, I have problem to add a new items to my Object useState,
I want to add items array of object a new item, can some one help me?
my useState :
const [kolom, setKolom] = useState({
["Main-Todo"]: {
columnName: "Todo",
items: [
{
id: v4(),
content: "Makan ayam di MCD 🍗",
},
{
id: v4(),
content: "Main bola sama presiden Putin ⚽",
},
],
},
[v4()]: {
columnName: "In-Progress",
items: [],
},
});
here's my code try to add items array of object in useState :
setKolom({
...kolom,
["Main-Todo"]: {
items: addTask,
},
});
}}
Thanks a lot, i new at react js

Use the functional update form to update the existing value and merge your change in with the current Main-Todo object
setKolom((prev) => ({
...prev, // current kolom
"Main-Todo": {
...prev["Main-Todo"], // current kolom["Main-Todo"]
items: addTask, // new items
},
}));
If by "add items array" you mean you want to append to the existing array as opposed to replacing it, you need only change one line...
items: prev["Main-Todo"].items.concat(addTask)
Using Array.prototype.concat() works if addTask is an array or a single object.

If you can use immerjs it can be written this way:
setKolom(produce(draft => {
draft["Main-Todo"].items.push(addedTask),
}))

Related

React object initial state is overriden

I got a react component with a form. I keep the form settings in an object outside the component:
const initialForm = {
name: {
elementType: 'input',
elementAtts: {
label: 'Tenant Name',
readOnly: false
},
isRequired : true,
value: '',
},
description: {
elementType: 'input',
elementAtts: {
label: 'Description',
readOnly: false
},
isRequired : false,
value: '',
}
}
const AddAndDisplay = (props) =>
{
const [formSettings, setFormSettings] = useState(initialForm);
...
}
the elementAtts is the attributes I pass the input.
What I'm trying to do is open a modal which displays the form - one time for display only and one time with editing allowed - can be for editing an existing item or for adding a new item.
I'm doing it like this for editing an existing item and for displaying:
//a callback
const OpenModalForEditOrDisplay = (isEditable, cardObject) =>
{
setFormSettings(prevForm =>
{
let newForm = {...prevForm};
newForm.name.elementAtts.readOnly = !isEditable;
newForm.description.elementAtts.readOnly = !isEditable;
return {...newForm}
});
setIsFormOpen(true);
}
};
and for adding a new item:
setFormSettings(initialForm);
setIsEditing(true);
setIsFormOpen(true); //this is merely a state saying if to show the modal with the form
The user can then submit or cancel the form, and on either case I'm doing:
setFormSettings(initialForm);
The problem is that it seems like initialForm is overridden and if I open the form for display only, it stays on display when trying to open the form for addition because the code for the edit part changed what I thought would be a copy of the initialForm. If I remove these lines in the open for edit function the form stays with the initial form's settings:
newForm.name.elementAtts.readOnly = !isEditable;
newForm.description.elementAtts.readOnly = !isEditable;
Why is the initial form being overridden here?
You have used Spread syntax to clone the prevForm values within setFormSettings. However you must note that Spread syntax only shallow clones the object and does not perform a deep cloning which means that you nested values within the prevForm still hold the original reference and when you update the values like
newForm.name.elementAtts.readOnly = !isEditable;
newForm.description.elementAtts.readOnly = !isEditable;
You are mutating it at the original reference. The correct way to update state is to immutably update it by cloning each nested level like
setFormSettings(prevForm =>
{
let newForm = {
...prevForm,
name: {
...prevForm.name,
elementAttrs: {
...prevForm.name.elementAttrs,
readOnly: !isEditable,
}
}
description: {
...prevForm.description,
elementAttrs: {
...prevForm.description.elementAttrs,
readOnly: !isEditable,
}
}
};
return newForm;
});
This is a problem for Deep Copy and shallow copy. The 'formSettings' data source is 'initialForm'. Use 'setFormSettings' will to change 'initialForm' , this is right. Because you use a shallow copy when you initialize it. you can use a function Deep Copy to 'initialForm'.
const createInitialForm = () => ({
name: {
elementType: 'input',
elementAtts: {
label: 'Tenant Name',
readOnly: false
},
isRequired : true,
value: '',
},
description: {
elementType: 'input',
elementAtts: {
label: 'Description',
readOnly: false
},
isRequired : false,
value: '',
}
})
const AddAndDisplay = (props) =>
{
const [formSettings, setFormSettings] = useState(createInitialForm());
...
}

Updating child array in reducer using React Context

I am doing some filtering using React Context and I am having some difficulty in updating a child's array value when a filter is selected.
I want to be able to filter by a minimum price, which is selected in a dropdown by the user, I then dispatch an action to store that in the reducers state, however, when I try and update an inner array (homes: []) that lives inside the developments array (which is populated with data on load), I seem to wipe out the existing data which was outside the inner array?
In a nutshell, I need to be able to maintain the existing developments array, and filter out by price within the homes array, I have provided a copy of my example code before, please let me know if I have explained this well enough!
export const initialState = {
priceRange: {
min: null
},
developments: []
};
// Once populated on load, the developments array (in the initialState object)
// will have a structure like this,
// I want to be able to filter the developments by price which is found below
developments: [
name: 'Foo',
location: 'Bar',
distance: 'xxx miles',
homes: [
{
name: 'Foo',
price: 100000
},
{
name: 'Bar',
price: 200000
}
]
]
case 'MIN_PRICE':
return {
...state,
priceRange: {
...state.priceRange,
min: action.payload
},
developments: [
...state.developments.map(development => {
// Something here is causing it to break I believe?
development.homes.filter(house => house.price < action.payload);
})
]
};
<Select onChange={event=>
dropdownContext.dispatch({ type: 'MIN_PRICE' payload: event.value }) } />
You have to separate homes from the other properties, then you can apply the filter and rebuild a development object:
return = {
...state,
priceRange: {
...state.priceRange,
min: action.payload
},
developments: state.developments.map(({homes, ...other}) => {
return {
...other,
homes: homes.filter(house => house.price < action.payload)
}
})
}

Why isn't the prop reactive in the dynamic component?

I have a dynamic component being injected in the slot of another one and I pass the props object to it. But when I update the data (an array) which has been assosiated with the prop (dataT: this.tableData), that prop isn't being updated inside the component.
It seems like I have a deal with two different objects but the array was passed by the reference, wasn't it?
This is the main component
<template>
<Button #click="addWindows"></Button>
<Window v-for="window in windows" :key="window.id">
<component :is="window.name" v-bind="window.props" #onDeleteRow="handleDeleteRow"></component>
</Window>
</template>
<script>
export default{
data(){
return{
windows:[],
tableData:[
{
id: '0',
name: 'dog'
},
{
id: '1',
name: 'cow'
},
{
id: '2',
name: 'cat'
}
]
}
},
methods:{
addWindows(){
this.windows = [
{
id: 0,
name: 'Component1',
props: {
dataT: this.tableData
}
},
{
id: 1,
name: 'Component2',
props: {}
}];
},
handleDeleteRow(id){
this.tableData = this.tableData.filter(r => r.id != id);
}
}
}
</script>
I expect updating dataT prop in Component1 when I modify this.tableData in the main component.
Original answer based on an earlier version of the question
If you make windows a computed property it can depend on tableData:
export default {
data() {
return {
tableData: [
{
id: '0',
name: 'dog'
},
{
id: '1',
name: 'cow'
},
{
id: '2',
name: 'cat'
}
]
}
},
computed: {
windows () {
return [
{
id: 0,
name: 'Component1',
props: {
dataT: this.tableData
}
}, {
id: 1,
name: 'Component2',
props: {}
}
]
}
}
}
If you can't make all of it a computed property, e.g. because you need to be able to modify it, then keep it as data and just use the computed property to create the array needed in your template. In that case the computed property would just be merging together different parts of the data into the correct form.
In your original code, the line dataT: this.tableData won't work because this.tableData doesn't exist yet, it'll just be undefined. There's no lazy evaluation here, it needs to resolve to the correct object at the point it hits that line.
Even if it was able to get access to the correct object it wouldn't help because in handleDeleteRow you're reassigning tableData to point to a different object. Passing 'by reference' has nothing to do with the name you use to identify the object, it refers to a reference in memory.
Incidentally, v-on also supports an object syntax, just like v-bind, so you could make onDeleteRow optional in a similar fashion.
Update based on the edited question
When you write this in addWindows:
props: {
dataT: this.tableData
}
This will assign the current value of this.tableData to dataT. That current value will be an array and as arrays are reference types any modifications made to that array will apply no matter what identifier is used to reference it.
However, this line...
this.tableData = this.tableData.filter(r => r.id != id);
... does not modify that array. Instead it assigns a totally new array to this.tableData. This will have no effect on the array referenced by dataT, which is unchanged.
There are several ways you could approach solving this, including using a computed property. However, a property getter might provide a convenient sleight-of-hand:
addWindows () {
const vm = this;
this.windows = [
{
id: 0,
name: 'Component1',
props: {
get dataT () {
return vm.tableData
}
}
},
{
id: 1,
name: 'Component2',
props: {}
}
];
}
This will always evaluate to the current value of tableData. Vue's reactivity should be fine with this extra indirection, it just sees it as equivalent to accessing tableData directly.
TL;DR
The issue is with your binding. Use the following:
<component
:is="window.name"
:dataT="window.props.dataT"
#onDeleteRow="handleDeleteRow">
</component>
Explanation
the v-bind attribute specifies what prop is bound to what value (or reference). In your case, you didn't specify what values you're binding to what props, thus the component props weren't bound as expected.

replacing an object of a collection using spread operator

I am trying to return a new columns object with updated tabs array and replace this object the existing one in my datasource. Here's the relevant part. Below, uses spread, however, it adds my object to the end of the columns. How can I make it replace the existing column?
Thanks!
newState = {
columns: [
{ id: column.id, title: column.title, tabs: removedTabs },
...state.columns
],
columnOrder: ["chromeTabs", ...state.columnOrder]
};
codesandbox link
newState = {
columns: [
...state.columns.filter(item => item.id !== column.id),
{ id: column.id, title: column.title, tabs: removedTabs }
],
columnOrder: [...state.columnOrder.filter(item => item !== 'chromeTabs'), "chromeTabs"]
};
return a filtred array and your new item, should do what you expect, using spread operator to replace existing item only work on object (because key are unique) not array.
const obj = {
cool: "is it cool ?"
};
console.log({ ...obj, cool: "definetly" });

Revert changes to array in Vue

I'm trying to have a component which can change some elements in it. In reality, a change will be like swapping the object in a given position. I did some POC and tried to do the reverting method to be able to leave it how it was before.
export default {
name: 'Landing',
data () {
return {
items: [
{
id: 1,
category: 'Something something'
},
{
id: 2,
category: 'Something something'
}
]
};
},
created() {
this.originals = this.items.slice(0);
},
methods: {
change() {
this.items[0].category = 'Changed';
},
revert() {
// ??
}
}
};
I've tried a couple of things especially after reading this: https://vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating
while (this.snacks.length) {
this.items.pop();
}
this.originals.slice(0).forEach(o => this.items.push(o));
But it doesn't work. If I delete the pushing part, I get an empty list correctly but if I try somehow to push it back, it won't work.
What am I missing here?
If you give a working fiddle I can show you what happened.
Basically, because you are modifying the same array object. this.originals refers to the same array as this.items
Slice returns a shallow copy of the object. You should either take a deep copy or your revert should be the one initializing the object.
this.items.pop();
will remove the items from this.originals as well.

Categories

Resources