React: Is 'previousState' considered mutable? - reactjs

I've been looking around for an answer to this questions for quite a while now, so I am just going to ask:
When passing a function as the first parameter to this.setState, is previousState mutable?
In the documentation it is stated that the function(state,props) can be used to update the state from the previous, but is it also ok to use the function like this:
Example: Assume state.profiles with user profiles and we want to change one user profile. So the question is about nested objects in state.
// Profiles has this structure:
// {1:{name: 'Old Name', age: 21}, 2: {name: 'Profile 2', age: 12}};
var changedProfile = {id: 1, name: 'New Name', age: 21};
this.setState(function (previousState) {
previousState.profiles[changedProfile.id] = changedProfile;
return previousState;
})
Is this ok? Can previousState.profiles be considered mutable?
Of course, the alternative would be to do something like this:
var changedProfile = {id: 1, name: 'New Name', age: 21},
newProfiles = _.extend({}, this.state.profiles);
newProfiles[chanedProfile.id] = changedProfile;
this.setState({profiles: newProfiles});
But if mutating previousState is OK, then it seems redundant to me to copy the object and then set state to the copied and changed object.
EDIT: Provided a bad example at first. Replaced with better example.

In your example your new state does not depend at all on the previous state, so you could just do:
this.setState({
a: 123;
b: {x: 'X', y: 'Y'};
})
However if that was just a bad example, and somehow you do need to check the previous state to determine the new state, then it would be something like this:
this.setState(function (previousState) {
return {
a: previousState.a + 123;
b: Object.assign(previousState.b, {x: 'X', y: 'Y'});
}
})
Essentially the result is that we return an object that is the new state, and we can use the previous state as we see fit - so yes, you can mutate the previous state as you did in your example

Related

How to find whether an object is of type formcontroller in angular js

I have a nested form. I want to validate it recursively. While validating, I need to check whether the object is of type Formcontroller or not. When trying instance of or type of, I am getting it as an object, not the form controller. Kindly help to find this.
If you want to know if an object is of a certain type, I believe you could ask something like this:
person = {
id: 1,
name: 'John',
age: 46,
active: true
};
house = {
color: 'blue with white patterns',
years: 20,
isInGoodCondition: true
};
const myArray = [person, house];
myArray.forEach((element) => {
if (element?.name) console.log('It is a person object');
if (element?.isInGoodCondition) console.log('It is a house object');
});
The object?.property asks safely if that property exists inside your object.

How to correctly update redux state in ReactJS reducer?

I have the following structure in my Redux data store:
{
filterData: {
22421: {
filterId: 22421,
selectedFilters: [
{
filterName: 'gender',
text: 'Male',
value: 'male'
},
{
filterName: 'gender',
text: 'female',
value: 'female'
}
]
}
22422: {
filterId: 22422,
selectedFilters: [
{
filterName: 'colour',
text: 'Blue',
value: 'blue'
},
{
filterName: 'animal',
text: 'sheep',
value: 'Sheep'
}
]
}
Can someone point me towards using the correct way to update the selectedFilters array without mutating the state directly? i.e. How can I add/remove elements in the selectedFilters array for a given filterId?
Generally it's done by using non mutating (ie. returning a new object, rather than modifying the existing one) operators and function:
spread operator (...) for objects and arrays (for additions and edits),
filtering, mapping and reduction for arrays (for edits and removals),
assigning for object (for edits and additions).
You have to do this on each level leading to the final one—where your change happens. In your case, if you want to change the selectedFilters on one of those objects you'll have to do something like that:
// Assuming you're inside a reducer function.
case SOME_ACTION:
// Returning the new state object, since there's a change inside.
return {
// Prepend old values of the state to this new object.
...state,
// Create a new value for the filters property,
// since—again—there's a change inside.
filterData: {
// Once again, copy all the old values of the filters property…
...state.filters,
// … and create a new value for the filter you want to edit.
// This one will be about removal of the filter.
22421: {
// Here we go again with the copy of the previous value.
...state.filters[22421],
// Since it's an array and we want to remove a value,
// the filter method will work the best.
selectedFilters:
state.filters[22421].selectedFilters.filter(
// Let's say you're removing a filter by its name and the name
// that needs to be removed comes from the action's payload.
selectedFilter => selectedFilter.name !== action.payload
)
},
// This one could be about addition of a new filter.
22422: {
...state.filters[22422],
// Spread works best for additions. It returns a new array
// with the old values being placed inside a new one.
selectedFilters: [
// You know the drill.
...state.filters[22422].selectedFilters,
// Add this new filter object to the new array of filters.
{
filterName: 'SomeName',
text: 'some text',
value: action.value // Let's say the value comes form the action.
}
]
},
}
}
This constant "copy old values" is required to make sure the values from nested objects are preserved, since the spread operator copies properties in a shallow manner.
const someObj = {a: {b: 10}, c: 20}
const modifiedObj = {...someObj, a: {d: 30}}
// modifiedObj is {a: {d: 30}, c: 20}, instead of
// {a: {b: 10, d: 30}, c: 20} if spread created a deep copy.
As you can see, this is a bit mundane to do. One solution to that problem would be to create some kind of nested reducers functions that will work on separate trees of the state. However, sometimes it's better not to reinvent the wheal and use tools that are already available that were made to solve those kind of problems. Like Immutable.js.
If you want to use a dedicated library for managing the immutable state (like suggested in another answer) take a look at Immer.
I find that this library is simpler to be used than Immutable.js (and the bundle size will be smaller too)

How do I preselect a vue-multiselect option when options is an array of objects?

I want to pre-select a particular value in a select drop-down generated by vue-multiselect.
I can get this to work fine if I have a simple array of strings like the following:
['Test 1', 'Test 2', 'Test 3']
However, when I use an array of objects, I can't get this to work. For example, if I have the following:
<v-multiselect :options="[{id: 1, name: 'Test 1'}, {id: 2, name: 'Test 2'}, {id: 3, name: 'Test 3'}]"
label="name"
track-by="id"
v-model="test">
</v-multiselect>
No matter what I set the test data property that v-model is connected to, it won't preselect the value. I've tried 1, 2, 3, '1', '2' and '3' for test when track-by is id and 'Test 1', etc. when track-by is name but nothing seems to work.
What am I doing wrong here? I looked at the docs at https://vue-multiselect.js.org/#sub-single-select-object, but they don't seem to provide an example when you want to preset a value for an array of objects for the options. Googling has also not returned what I'm looking for.
On a related topic, once I get this working, what would I have to change to select multiple values for when I set the component to multiple? Thank you.
track-by usage
The docs indicate that track-by is "Used to compare objects. Only use if options are objects."
That is, it specifies the object key to use when comparing the object values in options. The docs should actually state that track-by is required when the options are objects because <vue-multiselect> uses track-by to determine which options in the dropdown are selected and to properly remove a selected option from a multiselect.
Without track-by, you'd see two buggy behaviors for object-options: (1) the user would be able to re-select already selected options, and (2) attempting to remove selected options would instead cause all options to be re-inserted.
Setting initial values
<vue-multiselect> doesn't support automatically translating a value array, but you could easily do that from the parent component.
Create a local data property to specify track-by and initial multiselect values (e.g., named trackBy and initialValues, respectively):
export default {
data() {
return {
//...
trackBy: 'id',
initialValues: [2, 5],
}
}
}
Bind <vue-multiselect>.track-by to this.trackBy and <vue-multiselect>.v-model to this.value:
<vue-multiselect :track-by="trackBy" v-model="value">
Create a watcher on this.initialValues that maps those values into an object array based on this.trackBy, setting this.value to the result:
export default {
watch: {
initialValues: {
immediate: true,
handler(values) {
this.value = this.options.filter(x => values.includes(x[this.trackBy]));
}
}
}
}
Vue.component('v-multiselect', window.VueMultiselect.default);
new Vue({
el: '#app',
data () {
return {
trackBy: 'id',
initialValues: [5,2],
value: null,
options: [
{ id: 1, name: 'Vue.js', language: 'JavaScript' },
{ id: 2, name: 'Rails', language: 'Ruby' },
{ id: 3, name: 'Sinatra', language: 'Ruby' },
{ id: 4, name: 'Laravel', language: 'PHP' },
{ id: 5, name: 'Phoenix', language: 'Elixir' }
]
}
},
watch: {
initialValues: {
immediate: true,
handler(values) {
this.value = this.options.filter(x => values.includes(x[this.trackBy]));
}
}
}
})
<script src="https://unpkg.com/vue#2.6.6/dist/vue.min.js"></script>
<script src="https://unpkg.com/vue-multiselect#2.1.0"></script>
<link rel="stylesheet" href="https://unpkg.com/vue-multiselect#2.1.0/dist/vue-multiselect.min.css">
<div id="app">
<v-multiselect :track-by="trackBy"
:options="options"
v-model="value"
label="name"
multiple>
</v-multiselect>
<pre>{{ value }}</pre>
</div>
Looks like a bug. The workaround is to use an actual reference to the object
Vue.component('v-multiselect', window.VueMultiselect.default);
let testOptions=[{id: 1, name: 'Test 1'}, {id: 2, name: 'Test 2'}, {id: 3, name: 'Test 3'}]
new Vue({
el: '#app',
data: function () {
return {
test: testOptions[1], // <- use an object ref here!
testOptions
};
}
});
The easiest way I found out is sending the whole object from BE, so it gets pre-selected. If you send the same object from BE will get pre-selected. But I don't know if your options are hard coded on FE or they are coming from a database or something. I had the same issue but my values were coming from my database, so it was easy to reproduce the object
In your question just :object="true" is missing actually they didn't know that it is of type string or object when we pass this it knows yes it is object and i need label="name" from v-model="test" and picks it and shows it as a preselected

How to update value in a nested Immutable map

I am new to immutable.js and trying to figure out a way to update a nested map
Here is my object
let state = OrderedMap({
'name': Map({ id: 'name', hint: 'Search by name', value: '' }),
'job': Map({ id: 'job', hint: 'Search by job title', value: ''}),
'state': Map({ id: 'state', hint: 'Search by state', value: ''})
});
I am trying to set value of the 'name' object using a setIn function
state.setIn(['name', 'value'], 'Test');
The value is not getting updated as expected. Am I missing anything here
setIn doesn't mutate the original state, however it returns you another one so you would essentially have
state = state.setIn(['name', 'value'], 'Test');
I've never used immutable, but it looks to me like the docs are saying you want something more like:
setIn(state, ['name', 'value'], 'Test');
Where you are passing in the collection rather than running a function on it with a dot operator.
https://facebook.github.io/immutable-js/docs/#/setIn
Instead of mutating the state you can use setIn as this will return you a fresh copy instead , So you can do something like :
state = state.setIn(['name', 'value'], 'Test');
You can read more on Facebook Github

Detect when mobx observable has changed

Is it possible to detect when an observable changes in any way?
For instance, say you have this:
#observable myObject = [{id: 1, name: 'apples'}, {id: 2, name: 'banana' }]
And later on, with some user input, the values change. How can I detect this easily?
I want to add a global "save" button, but only make it clickable if that observable has changed since the initial load.
My current solution is to add another observable myObjectChanged that returns true/false, and wherever a component changes the data in the myObject, I also add a line that changes the myObjectChanged to true. And if the save button is clicked, it saves and changes that observable back to false.
This results in lots of extra lines of code sprinkled throughout. Is there a better/cleaner way to do it?
You could use autorun to achieve this:
#observable myObject = [{id: 1, name: 'apples'}, {id: 2, name: 'banana' }]
#observable state = { dirty: false }
let firstAutorun = true;
autorun(() => {
// `JSON.stringify` will touch all properties of `myObject` so
// they are automatically observed.
const json = JSON.stringify(myObject);
if (!firstAutorun) {
state.dirty = true;
}
firstAutorun = false;
});
Create an action that will push to myObject and set myObjectChanged
#action add(item) {
this.myObject.push(item);
this.myObjectChanged = true;
}
As capvidel mentioned, you can use autorun to track if variable changes, but to avoid adding additional variable firstAutorun you can replace autorun with reaction:
#observable myObject = [{id: 1, name: 'apples'}, {id: 2, name: 'banana' }]
#observable state = { dirty: false }
reaction(
() => JSON.stringify(myObject),
() => state.dirty = true
);

Resources