I've seen the use of setIn() and set() in some react-redux code:
state.setIn(...);
state.set(...);
I've found some documentation here https://facebook.github.io/immutable-js/
But unfortunately the method is not documented in detail.
I also found some other questions: Using React's immutable helper with Immutable.js
But these do not answer my question.
I understand, that it must do some immutable stuff?
But what's the immutable thing here?
And what's the difference between set() and setIn()?
Why do we need immutable?
Immutable set method only sets immediate properties, I.e. direct children of the object. A setIn let's you set the value of any deep node down the data. set only takes property name. setIn takes an array of keys/index to reach down to the deeply nested element.
var basket = Immutable.Map({"milk":"yes", "flour":"no"});
basket = basket.set("flour", "yes");
basket = Immutable.Map({"fruits":{"oranges":"no"}, "flour":"no"});
basket = basket.setIn(["fruits", "oranges"], "yes");
The getIn/setIn methods are extremely useful when updating states in stores as you can use generic actions and supply the key paths to child components. They can invoke the actions passing the paths as parameters.
set and setIn are one of the immutablejs method which you use to set data in a list or map object. simple example to understand this is lets say you have a this
//note that fromJS is another method which comes from immutablejs library
const iniState = fromJS({
name:null,
friends:fromJS({
name:null
}),
})
in this case you need to update the initial state with the latest then that's where you can use set and setIn methods.
iniState.set('name',"sibusiso Massango").setIn(['friends','name'],"Zweli Mathebula");
this is how you can use the set and setIn method, to find more about this you can read this docs https://facebook.github.io/immutable-js/docs/
Related
Expected to achieve:
Most correct way to modify specific record stored in the Vuex state with one big (batch) write
Context:
Vuex state contains list of records with default values for each record.
Logic
Component initialises and uses getter to get one of the records.
It's needed to add new properties to the record and overwrite existing values.
Question
Is it acceptable to modify the object returned by the Vuex getter and later commit the whole result into the state? And if yes, what would be the best approach considering it will have to overwrite existing record in Vuex.
P.S: I also wonder if it can result in breaking behaviour of other components that are "getting" the same record that will be overwritten, and will appreciate a lot your thoughts on this topic :-)
Don't modify Vuex data except via Vuex mutation
1) First, clone the getter record. Make sure you deep clone if your record has anything but primitive properties, i.e it has properties that are objects or functions. Here are some cloning options, notice that many don't correctly deep clone.
2) Once you're done mutating the clone, call a Vuex action and have that action call a mutation. It's good practice to only call mutations from actions.
3) In that mutation, use Vue.set (or splice) to replace the state record.
4) Yes, if you mutated the getter directly, it would be mutated anywhere else it was used.
See Change detection caveats in the VueJS reactivity guide.
Sometimes you may want to assign a number of properties to an existing object, for example using Object.assign() or _.extend(). However, new properties added to the object will not trigger changes. In such cases, create a fresh object with properties from both the original object and the mixin object:
// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
I have array named List and created computed property computedList for him.
When i update value of array it's not showing in html, but in console i see thar array is updated.
`https://jsfiddle.net/apokjqxx/69/`
What is best way to use computed properties for array?
Maybe is exists way to trigger to re-render computed property?
Due to limitations in JavaScript, Vue cannot detect the changes to an array like this: this.list[1] = 'vueman'
You have to use Vue.set or vm.$set as explained here to trigger state updates in the reactivity system, like follwoing:
this.$set(this.list, 1, 'vueman')
see updated fiddler here.
I'm using #ngrx/store for an Angular 2 app.
My store holds a list of say, Book objects. I want to update a field in one of those objects. I also happen to have an Observable of the Book instance I'm looking to update (say, selectedBook).
To do the update I intend on calling the reducer with an UpdateBookAction, and a payload of the new Book. So I make a deep copy of the existing Book object by subscribing to selectedBook and then calling Object.assign().
But when I try to write to one of the fields of the copy I get the following error. (It happens to be the same error I get if I were to try to write directly to the Book object in the store.)
Error
Cannot assign to read only property 'name' of object '#<Object>' at ViewWrappedError.BaseError [as constructor]
Code
ngOnInit() {
this.book$ = this.store.let(fromRoot.getSelectedBook);
//...
}
someFunction() {
//...
this.book$.subscribe(book => {
let updatedBook = Object.assign({}, book);
updatedBook.name = 'something else'; // <--- THIS IS WHAT THROWS
let action = new BookUpdateAction(updatedBook);
this.store.dispatch(action);
}
}
Clarification after Comments
I was under the assumption that I could have an action with a payload that was not the entire state of the store. (In fact that seems necessary, no?) I'm confident that this is the case given the documentation.
The action I'm looking to take is something like this:
Action = UPDATE, payload = {'id': 1234, 'name': 'something new'}
As mentioned, I intend on making that call like this:
this.store.dispatch(action);
Presumably under the hood, ngrx is passing my action to the reducer along with the (immutable) current state.
So from there, everything should work okay. My logic inside the reducer doesn't mutate the existing state, it simply creates a new one out of the existing state and the payload I've passed in.
The real question here is how I can reasonably build the new "objectToUpdate" such that I can pass that in as the payload.
I could do something like this:
this.book$.subscribe(book => {
let updatedBook = new Book();
updatedBook.id = book.id;
//set all other fields manually...
updatedBook.name = 'something else';
let action = new BookUpdateAction(updatedBook);
this.store.dispatch(action);
}
But we're not just talking about two fields here... what if my book has several fields? Do I have to manually build from scratch a new Book each time just to update one field?
My solution was to do a deep copy using Object.assign({}, book) (and not mutate the old one!) and subsequently make the update to solely the field I was looking to touch.
The idea of the ngrx store is to have one and only one single place of truth, which means all the objects are immutable, and the only way to change anything is to recreate everything as a whole. Also, you are probably using the ngrx freeze (https://github.com/codewareio/ngrx-store-freeze) which means that all of the objects will be created read-only so you wont be able to change any (This is good for development if you want to completely follow the redux pattern). If you remove the part where the store freezes the object, you will be able to change it, but thats not best practice.
What I would suggest you is the following: Use the ngrx observable with async pipe to put the data (in your case books) in a dumb component which can only get input and output some event. Than, inside of the dumb component you can "edit" that object by making a copy of it, and after you are done, you can emit back the changes to the smart component which is subscribed to the store and allow it to change the state via the store (commit). This way is best because it is not very common to change the whole state for a really small change (like two way binding, when user types..).
If you follow the redux pattern, than you will be able to add history, which means the store will keep a copies of the last X state recreations, so you can get UNDO functionality, easier to debug, timeline etc
Your problem is that you are directly editing the property instead of recreating the whole state.
I'll have to make an assumption about the actual scenario the OP is experiencing.
The problem
It's not possible to modify a member of a frozen object. Its the error being thrown.
The cause
ngrx-store-freeze is used as a meta-reducer to freeze any object that enters the store. On another place, when an object needs to be changed, a shallow copy is being made. Object.assign() doesn't do deep copy. A member of another object reached from the original object is being modified. This secondary object is also frozen, by it is not duplicated.
Solution
Use a deep copy like cloneDeep() from lodash. Or sent a bag of properties to be changed with a proper action. Process the changes on the reducer.
As already mentioned - the reason you are getting
Cannot assign to read only property 'name' of object
is because 'ngrx-store-freeze' freezes the state and prevents mutating it.
Object.assign will provide a new object as you expect, but it will copy the state's properties along with each property's own definition - such as the 'writable' definition (which 'ngrx-store-freeze' likely sets to false).
A different approach is described in this answer and explains how cloning objects with JSON.parse(JSON.stringify(yourObject)) as fastest, but this approach has flaws if you keep dates or methods etc' in your state.
using lodash's 'cloneDeep' is probably your best bet for deep cloning the state.
One way to accomplish this is a utility/helper method to make a new book from.
You could give it an existing book and the subset of properties you want to add to a new book (using Partial in typeScript if you want type safety).
createNewBook(oldBook: Book, newProps: Partial<Book>): Book {
const newBook = new Book();
for(const prop in oldBook) {
if(newProps[prop]) {
newBook[prop]=newProps[prop];
} else {
newBook[prop]=oldBook[prop];
}
}
return newBook
}
You could call it via newBook = createNewBook(new Book(), {title: 'first foo, then bar'});
and use this newBook to update your store.
So I have this computed property inside my component.js: contexts: Ember.computed.oneWay('myService.contexts'),
And I am able to get the content from another action
openHelp(){
console.log(this.get('contexts'))
alert(this.get('contexts'))
}
}
But when I try to use the computed property in Handlebars ({{contexts}}) it's just blank.
I created an Ember Twiddle for this question: https://ember-twiddle.com/38de64d58dcf3298df6d4176f15cbc0e?openFiles=components.my-component-help.js%2Ctemplates.components.my-component-help.hbs
If I have an array foo: [ 'foo','bar'] and I do {{foo}} it outputs in handlebars. But if I make foo a computed property that gets [ 'foo','bar'] from and do {{foo}} I get nothing.
Here's the solution: https://ember-twiddle.com/e9c2ef05e27013a389e0b2bfdaec3d40?openFiles=services.my-service.js%2Ctemplates.components.my-component-help.hbs
There were two issues:
contexts is an array. When you console.log or alert it, those methods internally in some browsers JSON.stringify the object for you for your convenience. Ember will not do that. You need to format the array yourself or, as I did, each over it. For debugging purposes, feel free to use the log helper.
Computed properties on arrays are watching for array mutations through Ember's methods such as pushObject and removeObject. Simply using push or splice won't update the computed property.
Can't comment on the above answer which is correct because I don't have enough reputation, but I wanted to add a link to the documentation relating to Ember's observable methods for enumerables:
https://guides.emberjs.com/v2.5.0/object-model/enumerables/
I'm trying to setup a property on an object that will be an object, like this.
Say I have the property called cities, and I want to have a value of population, on a normal object I would do
cities: {
'city1': 100,
'city2': 200
}
now if I want to add city3 i could do
this.get('cities')['city3'] = 300
while this will update the object it won't propagate the bindings, I could make it an array and just use pushObject, but I would rather have it so that if inputing info about a particular city, if it already exists, it just updates the old data.
So is there a way to have that and being observable?
Thanks
Edit even using the
this.set('cities.city1',100)
won't notify on changes as I can observe #each, I used the trick on this answer to get it to work.
Observe properties on nested object
Using = will not trigger any of the observers and bindings, you need to use set example:
this.set('cities.city3', 300);
You should be using set instead of =.
this.set('cities.city3', 300);