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());
...
}
Related
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),
}))
I am trying to edit this initial state:
const initialFilters = {
PGfenced: '',
PGgrill: '',
PGrestrooms: '',
PGstaffed: '',
PGtoddler: '',
Animals: '',
};
I have this so far and it just adds another object onto the end. I want to edit and replace just part of the initialFilters.
const [playgroundFilters, setPlaygroundFilters] = useState([initialFilters]);
const updateItem = (whichvalue, newvalue) => {
setPlaygroundFilters({ ...playgroundFilters, [whichvalue]: newvalue });
console.log(playgroundFilters);
};
onPress={(value) => updateItem('PGfenced', value)}
options={[
{ label: 'No Fence', value: '' },
{ label: 'Partially', value: 'Partially fenced' },
{ label: 'Fully', value: 'Fully fenced' },
]}
Your state variable is an array, but then you assign an object to it. I assume you want it to be an object since you're updating it with a new value, just remove the square brackets in the state declaration like this:
const [playgroundFilters, setPlaygroundFilters] = useState(initialFilters);
And then you will be able to update that object the way you did.
I have no idea if what I'm doing is correct or not, but here's a simplified version of what I'm trying to do:
I want to have 3 file inputs, with the 2nd and 3rd disabled until the 1st one has had a file selected.
I've tried to do is set the Vuex state variable to whatever the first file input is has selected, but upon doing that the other 2 inputs don't update their disabled state.
I have some file inputs that are created dynamically, like so:
Vue.component('file-input', {
props: ['items'],
template: `<div><input type="file" v-on:change="fileSelect(item)" v-bind:id="item.id" v-bind:disabled="disabledState"></div>`,
methods: {
fileSelect: function(item) {
store.commit('fileSelect', file);
}
},
computed: {
disabledState: function (item) {
return {
disabled: item.dependsOn && store.getters.getStateValue(item.dependsOn)
}
}
}
}
The data for the component is from the instance:
var vm = new Vue({
data: {
items: [
{ text: "One", id: "selectOne" },
{ text: "Two", id: "selectTwo", dependsOn: "fileOne" },
{ text: "Three", id: "selectThree", dependsOn: "fileOne" }
}
});
Now, notice the "dependsOn". In the Vuex store, I have a corresponding state item:
const store = new Vuex.Store({
state: {
files: [
{
fileOne: null
}
]
},
mutations: {
fileSelect(state, file) {
state.files.fileOne = file;
}
},
getters: {
getStateValue: (state) => (stateObject) => {
return state.files.findIndex(x => x[stateObject] === null) === 0 ? true : false;
}
}
});
Now, the above works when everything is first initialized. But once the first input has something selected, the other two inputs don't change.
I'm not sure how to update the bindings once a mutation of the state occurs.
I think you need to refactor your mutation to make the state property mutable, like this:
fileSelect(state, file) {
Vue.set(state.files[0].fileOne, file);
}
Well, I figured it out...
Because my state object is an array of objects, I can't just change one of the property's values with state.files.fileOne. I needed to do state.files[0].fileOne.
I am creating arrays of objects and storing it on variables like this:
const nameOption = nameOptions(listHotels);
const estadoOption = stateOptions(listHotels);
const cityOption = cityOptions(listHotels);
my state is currently like this:
selectFilter: [
{ id: 1, type: 'Name'},
{ id: 1, type: 'Estado'},
{ id: 1, type: 'Cidade'},
],
I want to add these variables in a property called "options", like so:
selectFilter: [
{ id: 1, type: 'Name', options: nameOption},
{ id: 1, type: 'Estado', options: estadoOption},
{ id: 1, type: 'Cidade', options: cityOption},
],
how do I do it using the immutable react way?
First store the values to be inserted into a plain object, where the properties match the type values of selectFilter :
const options = {
Name: nameOptions(listHotels),
Estado: stateOptions(listHotels),
Cidade: cityOptions(listHotels)
}
Then merge that with selectFilter into the extended version of it:
this.setState(prevState => ({
selectFilter: prevState.selectFilter.map(filter =>
({...filter, options: options[filter.type]})
)
}));
NB: there is probably a more efficient way to build the options object, since you seem to iterate listHotels for each property. This could probably be done in one sweep using reduce. But without details about what these functions (nameOptions, stateOptions, ...) do there is not much I can offer for that. Look into calling listHotels.reduce.
You can use Object.assign() to duplicate your state into an new object. This object is now mutable. Once done modifying it, you then replace your entire state with the new version. Here is an example of how that could be done.
handleChange= () => {
let mutableState = Object.assign({}, this.state);
mutableState.thingToChange = foo;
this.setState(mutableState);
};
this.setState(({selectFilter}) => (
[
{...selectFilter[0], options: nameOptions},
{...selectFilter[1], options: estadoOptions},
{...selectFilter[2], options: cityOptions},
]
);
I can't seem to get my head around how to efficiently set all the radio buttons in a redux state to false then set the original action creator to true.
Right now I struggle by trying to copy the state object using Object.assign whilst also providing a way to change the objects which hold true for item.type === 'radio' to false.
Some code I thought might be close to the true way.
const state = {
'some_id_1': {
type: 'radio',
id: 'some_id_1',
checked: true
},
'some_id_2': {
type: 'radio',
id: 'some_id_2',
checked: false
},
'some_id_3': {
type: 'checkbox',
id: 'some_id_3',
checked: false
}
};
const state = Object.assign({}, state, (item) => {
if(item.type === 'radio') {
console.log('reset='+item);
item.checked = false;
}
return item;
});
return state;
But of course this doesn't work as it's not how Object.assign works.
Of course I could also loop over the object's keys, i.e. 'radio_box_1_id', ... and set anything with item.type === 'radio' to false, or even bring these changed values out to a separate state object and replace it using another argument of Object.assign. But these seem like extra processing time and not the perfect solution I'm looking for.
Thanks
Edit: My goal is to set the items of my state to a state of NOT checked, if they are a radio, but try not to change the others. I want to do this efficiently if it's possible. I can set the right radio afterwards to true.
Although you didn't specify the state shape nor your reducer code, this is a common operation.
If i understand correctly, you have an array of items (objects) with a type and id for each.
If the type is a radio then you will have a checked property.
If this is correct, you can use the Array.prototype.map() method and conditionally return a new object (using the object spread) while you check the id inside your action payload.
Here is a small example:
const state = [{
type: 'radio',
id: 1,
checked: true
}, {
type: 'radio',
id: 2,
checked: false
}, {
type: 'text',
id: 88,
value: 'hi there'
}, {
type: 'radio',
id: 43,
checked: false
}];
const action = {
id: 2
};
console.log('before the change', state);
const nextState = state.map((item) => {
if (item.type === 'radio') {
return {
...item,
checked: item.id === action.id
}
}
return item; // not a radio, go on.
});
console.log('after the change', nextState);
Update
As a followup to your update, now that we know the shape of your state, when dealing with objects instead of arrays, you can iterate over the keys with Object.keys and use Array.prototype.reduce() to create a new object according to your conditions:
const state = {
'some_id_1': {
type: 'radio',
id: 'some_id_1',
checked: true
},
'some_id_2': {
type: 'radio',
id: 'some_id_2',
checked: false
},
'some_id_3': {
type: 'checkbox',
id: 'some_id_3',
checked: false
}
};
const action = {
id: 'some_id_2'
};
console.log('before the change', state);
const nextState = Object.keys(state).reduce((result,currentKey) => {
const currentItem = state[currentKey];
if (currentItem.type === 'radio') {
result[currentKey] = {
...currentItem,
checked: currentItem.id === action.id
}
} else{
result[currentKey] = currentItem // not a radio, go on.
}
return result;
},{});
console.log('after the change', nextState);
Edit
As a followup to Ulysse BN's comment:
I had to downvote: your modified answer doesn't fully clone the state.
if after your code I run state['some_id_3'].foo = 'bar', nextState
will be modified as well since they share the same object reference.
This is misleading, if you are not mutating an object then there is no need to re-create it. This is not a mutation.
This is a common practice in redux, you can learn more about it in Updating an Item in an Array