I have
this.state = {
modal_1: true,
modal_abc: true,
modal_special: true
}
how can I change everything that start with modal to false? is it possible with
this.setState({
`modal_*`: false
})
There is no such thing as wildcards in React's setState method or javascript's object literal. You can manualy iterate over object keys and reduce it, e.g.:
const newState = Object.keys(this.state).reduce((result, key) => {
// conditionally set value of result
result[key] = key.startsWith('modal_') ? false : this.state[key];
return result;
}, {});
// and set new state
this.setState(newState);
Related
I want to update value of one object only but updating value of one Object, Updates the value for all objects.
let default = {
name: '',
age: ''
}
this.state = {
values: Array(2).fill(default)
}
updateName (event) {
let index = event.target.id,
values = this.state.values;
values[index].name = event.target.value;
this.setState ({
values: values
});
}
There are four significant problems in that code.
You're using the same object for all entries in your array. If you want to have different objects, you have to create multiple copies of the default.
You're calling setState incorrectly. Any time you're setting state based on existing state (and you're setting values based, indirectly, on this.state.values), you must use the function callback version of setState. More: State Updates May Be Asynchronous
You can't directly modify the object held in this.state.values; instead, you must make a copy of the object and modify that. More: Do Not Modify State Directly
default is a keyword, you can't use it as an identifier. Let's use defaultValue instead.
Here's one way you can address all four (see comments):
// #4 - `default` is a keyword
let defaultValue = {
name: '',
age: ''
};
this.state = {
// #1 - copy default, don't use it directly
values: [
Object.assign({}, defaultValue),
Object.assign({}, defaultValue),
] // <=== Side note - no ; here!
};
// ....
updateName(event) {
// Grab the name for later use
const name = event.target.value;
// Grab the index -- I __don't__ recommend using indexed updates like this;
// instead, use an object property you can search for in the array in case
// the order changes (but I haven't done that in this code).
const index = event.target.id;
// #2 - state updates working from current state MUST use
// the function callback version of setState
this.setState(prevState => {
// #3 - don't modify state directly - copy the array...
const values = prevState.values.slice();
// ...and the object, doing the update; again, I wouldn't use an index from
// the `id` property here, I'd find it in the `values` array wherever it
// is _now_ instead (it may have moved).
values[index] = {...values[index], name};
return {values};
});
}
Note that this line in the above:
values[index] = {...values[index], name};
...uses property spread syntax added in ES2018 (and shorthand property syntax, just name instead of name: name).
I would use the Array.prototype.map function with combination of the object spread syntax (stage 4):
Note that i changed the name of the default object to obj.
default is a reserved key word in javascript
let obj = {
name: '',
age: ''
}
this.state = {
values: Array(2).fill(obj)
}
updateName(event){
const {id, value} = event.target;
this.setState(prev => {
const {values} = prev;
const nextState = values.map((o,idx) => {
if(idx !== id)
return o; // not our object, return as is
return{
...o,
name: value;
}
});
return{
values: nextState
}
});
}
There is an easy and safe way to achieve that through the following:
this.setState({
values: [ newObject, ...this.state.values],
});
this will create an instance of the state and change the value of an existing object with new object.
In componentDidMount i did this:
apps.forEach(app => {
if (chosenAppId) {
if (app.id === chosenAppId) {
this.setState({ map: this.props.map });
}
}
});
and now when i do this in some function:
this.setState({
...this.state.map,
areas: this.state.map.areas.map(el =>
el._id === area._id
? Object.assign(el, {
chooseDevice: false,
editModal: true
})
: Object.assign(el, { chooseDevice: false })
)
});
I have persistent redux state, in this case I would expect that on reload this.state.map === this.props.map but somehow this object.assign mutated my redux state and on reload all is saved to reducer.
I narrowed it down that it has something to do with object.assign because if I .concat() something to this.state.map, that does not changes redux state.
How? I really do not get it. No redux action is dispatched, do not know how this can happen.
The line Object.assign(el, { chooseDevice: false }) will mutate el.
It looks like you copied that from props (and thus likely the Redux store) into state. So, it's the same object reference that was already inside the Redux store, and thus you're mutating the value that's in the store.
Note that our official Redux Toolkit package includes a mutation detection middleware by default that will throw errors when you accidentally mutate values.
You're absolutely right about the root cause of the issue - Object.assign() was mutating your original array items which you were referring to within map().
To resolve this, simply get rid of Object.assign() mutating your state:
this.setState({
...this.state.map,
areas: this.state.map.areas.map(el => ({
...el,
chooseDevice: false,
...(el._id === area.id && {editModal: true})
}))
});
When you spread an object it doesn't clone existing properties, it simply copies them into the new object, meaning instance properties will remain e.g.
const obj = { numbers: [1, 2, 3], person: { name: 'Foo' } }
const copy = { ...obj };
copy.numbers.push(4);
copy.person.name = 'Bar'
console.log(obj.numbers) // [1,2,3,4]
console.log(obj.person) // { name: 'Bar' }
console.log(copy.numbers) // [1,2,3,4]
console.log(copy.person) // { name: 'Bar' }
Notice how the original object has been updated by changes made to the copy
Therefore, when you spread your state into the local state i.e.
...this.state.map
And then use Object.assign on the instance properties, you are inadvertently updating the Redux state at the same time.
With Object.assign()
The Object.assign() method copies all enumerable own properties from one or more source objects to a target object.
Object.assign(target, ...sources)
target
The target object — what to apply the sources’ properties to, which is returned after it is modified.
sources
The source object(s) — objects containing the properties you want to apply.
Object.assign() - JavaScript | MDN
If you'd like it not to alter the element provided, target an empty object. That way el and the additional data in the third argument will be assigned to a new object, instead of overriding properties of el.
this.setState(prevState => ({
...prevState.map,
areas: prevState.map.areas.map(el =>
el._id === area._id
? Object.assign({}, el, {
chooseDevice: false,
editModal: true
})
: Object.assign({}, el, { chooseDevice: false })
)
}));
With object spread (compact)
If you'd like to make ir more compact you can also turn it into an object spread, and use an inline if for the conditional change in editModal.
this.setState(prevState => ({
...prevState.map,
areas: prevState.map.areas.map(el => ({
...el,
chooseDevice: false,
editModal: el._id === area._id ? true : el.editModal
}))
}));
EDIT: this.state should not be used in setState
Let's say a component has state such as:
this.state = {
enabled: {
one: false,
two: false,
three: false
}
}
How can this.setState() be used to set the value of a dynamic property?
For instance, this does not work:
let dynamicProperty = "one"
this.setState({
enabled[dynamicProperty]: true
})
However, this does work, but is also bad practice:
this.enabled = {
one: false,
two: false,
three: false
}
let dynamicProperty = "one"
this.enabled[dynamicProperty] = true;
How can this.setState() be used to accomplish the same thing?
You need to create a copy of the original object and only change the property you want to update. The easiest way to do that is to use the object spread operator:
this.setState(currentState => ({enabled: {...currentState.enabled, one: true}}));
or in a more verbose form:
this.setState(currentState => {
const enabled = {...currentState.enabled, one: true};
return {enabled};
});
If the property name is only known at runtime you can do it like this:
const setEnabled = name => {
this.setState(currentState => ({enabled: {...currentState.enabled, [name]: true}}));
};
The standard practice is to copy the the state, modify the copied state, then set state using that clone, like this:
//with spread operator
const enabledClone = {...this.state.enabled};
enabledClone.one = true;
this.setState({enabled : enabledClone});
You can use braces around an object's key to use a variable to determine the key
const dynamicKey = 'one';
const newObj = {[dynamicKey]: true} //equals {one: true}
Since this.setState only merges on toplevel keys, you will have to create a copy of the current enabled object and use the braces notation:
let dynamicProperty = "one"
this.setState({
enabled: {...this.state.enabled, [dynamicProperty]: true}
})
I am trying to do filter an array based on flag isSome: true. I want to filter when that flag is not present in that array
var value = [
{ "somevalues": {},
"moreDetails": {
"isSome": "true"
}
},
{ "somevalues": {},
"moreDetails": {}
},
{ "somevalues": {},
"moreDetails": {}
},
]
const valuewithisSome = value.filter(o => o.moreDetails && o.moreDetails.isSome);
const valuewithoutisSome = value.filter(o => o.moreDetails && !o.moreDetails.isSome);
console.log(valuewithisSome);
console.log(valuewithoutisSome);
valuewithisSome is working as expected and returning array with isSome: true.
valuewithoutisSome is not working as expected as i don't want to pass isSome false in morevalues array, is there a way to filter without even passing that flag?
The "without" code you already have should work:
const valuewithoutisSome = value.filter(o => o.moreDetails && !o.moreDetails.isSome);
The expression o.moreDetails.isSome will evaluate to undefined if the "isSome" property is missing or explicitly false-y, so !o.moreDetails.isSome will be true in that case.
Now while that will work, if you want to explicitly test for the property being false only when it's actually present, you can do this:
const valuewithoutisSome = value.filter(o =>
o.moreDetails &&
(!("isSome" in o.moreDetails) || !o.moreDetails.isSome)
);
That will only return false (and filter out the array entry) when the "isSome" property is present. If it's not, or if it's present and true, then the entry will be in the filtered result.
Your isSome property type is string. Remove double quetos from true.
{ "somevalues": {},
"moreDetails": {
"isSome": true
}
},
came across this code learning redux and react.
my question is what the final result looks like when the spread oeprator is used. if I understand it correctly it basically turns iterable or array into individual arguments.
so I expect that the output is just creating another JSON object using all the fields obtained via the ... operator.
const INITIAL_STATE = { postsList: {posts: [], error:null, loading: false},
newPost:{post:null, error: null, loading: false},
activePost:{post:null, error:null, loading: false},
deletedPost: {post: null, error:null, loading: false},
};
export default function(state = INITIAL_STATE, action) {
let error;
switch(action.type) {
case FETCH_POSTS:// start fetching posts and set loading = true
return { ...state, postsList: {posts:[], error: null, loading: true} };
so is this the result of FETCH_POSTS:
{
postsList: {posts: [], error:null, loading: false},
newPost:{post:null, error: null, loading: false},
activePost:{post:null, error:null, loading: false},
deletedPost: {post: null, error:null, loading: false, },
so basically it was smart enough to know that there existed the postsList key and overwritten it?
now is it anti-pattern to rely on checking the existence of the state for the react.js app? meaning "if this key exists do this" instead of "if key value is null do this"
why don't you just change the value of the key via array key? INITIAL_STATE['postsList'] = {...postsObject}
or is using the spread operator the new pattern?
When using Redux you want to treat the state as immutable. The reducer should return a new state object, not modify the existing one. If you run INITIAL_STATE['postLists'] = somethingNew; you are modifying the object referenced by INITIAL_STATE.
Instead of using the spread operator in this case is to use Object.assign, which will create a new object based on an existing object and overload some properties.
That being said, you should not really use INITIAL_STATE at all except as the default value of state in the reducer definition.
Object.assign and ... spread operator is good for shallow copying. For deep copying, I prefer lodash/deepClone or deepCopy util like this.
export default function deepCopy(obj) {
if (typeof obj === 'object' && Object.prototype.toString.call(obj) === '[object Array]') {
let finalObj = [];
for (let i = 0, len = obj.length; i < len; ++i) {
finalObj.push(deepCopy(obj[i]));
}
return finalObj
} else if (typeof obj === 'object') {
let finalObj = {};
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
finalObj[prop] = deepCopy(obj[prop]);
}
}
return finalObj;
} else {
return obj;
}
}
I blindly deep-copy my state to a new state. And then apply Object.assign to various objects. So, my code will be:
const newState = deepCopy(state);
Object.assign(newState.prop1, { innerProp: 'val' });