push function changes props in react - javascript

this is my problem.
when I use the push() function it changes my props in react.
const prioship = (this.props.orders.prioshipping === false ? {'name':`Regular Shipping`,'price':'0','currency':'EUR','quantity':1, 'description': ''} : {'name':`Priority Shipping`,'price': this.props.prices.shipping['A'].toString() ,'currency':'EUR','quantity':1, 'description': ''})
console.log('#### TOKEN ORDER #####1', this.props.orders.warenkorb)
const orders = this.props.orders.warenkorb
const order2 = this.props.orders.warenkorb
orders.push(prioship)
console.log('#### TOKEN ORDER #####2',order2, this.props.orders.warenkorb)
So even at the level of the console log 'TOKEN ORDER 1' this props have the "prioship" in it even though it happens later in the code. I don't understand how to make it stop this. I just want a variable 'orders' where the prioship is in it, i dont want my props to change.
Please help

Never mutate props, which you're doing here.
Make a new array instead, and don't modify the original array.
const orders = this.props.orders.warenkorb.concat( prioship );
Array.push() "mutates" (changes/modifies) the original array. Array.concat() returns a new array, and does not modify the original.

As Andy Ray mentioned, don't change the props directly.
The right way is to use const orders = this.props.orders.warenkorb.slice(), which will give you a copy of the array in the props and allow you to use this array later without changing the original props.
Lastly, the reason your 1st console.log('#### TOKEN ORDER #####1', this.props.orders.warenkorb) is showing you the later value is because the console will show the values by reference. If you want the exact value at where you're printing you can use: console.log('#### TOKEN ORDER #####1', JSON.stringify(this.props.orders.warenkorb));

Two things are happening here.
First of all you are modifying a prop in an object. even if you save the property in another variable all you are doing is saving a reference to that property. If you want to avoid that you can use concat as Andy Ray pointed out.
Second thing. You see the change even at TOKEN ORDER 1 because console.log can be a bit tricky. Since objects store references to their properties, if you change the object later it will show you the latest update. More on this here.

Related

Error writing to input e.target.value in array

There is an array of inputs in two forms: 1. Empty when created. 2. With the created values from the server after creation and saving. My code for adding a value for each of the inputs in the array doesn't work in the second case and I don't understand what could be wrong. In the first case, the values are written normally, and in the second, nothing happens.
<input
defaultValue={sectionValue[i].text}
value={sectionValue[i].text}
onChange={(e: React.ChangeEvent<HTMLInputElement>): void => {
sectionValue[i].text = e.target.value;
setSectionValue([...sectionValue]);
}}
/>
There are two issues:
Any time you're changing state based on existing state, you're best off using the callback form of your state setter, because it's really easy for the handler to close over a stale copy of the state (sectionValue in your case).
You're breaking one of the rules of state: don't directly modify state. You're directly modifying the sectionValue[i] object. So it's the same object, but with a different text property. Later you're copying the array, but you need to copy the object as well. Sometimes it'll happen to render correctly when you do this, but other times — often — it won't.
To fix both, change:
sectionValue[i].text = e.target.value;
setSectionValue([...sectionValue]);
to
setSectionValue(oldSectionValue => {
const newSectionValue = [...oldSectionValue];
newSectionValue[i] = {
...oldSectionValue[i],
text: e.target.value
};
return newSectionValue;
});
There's more than one way to do that, but the basic things are: 1. Use the callback, and 2. Copy both the object and the array.
Side note: Since sectionValue is an array, I'd suggest using the plural for it (sectionValues).

React hook useState mutates a constant array

I'm using React hooks to implement a form. The state of the form is being managed as an array of objects. A feature/bug of React appears to be that copies of arrays are processed/unified/mutated as a single array. That is, changes to one array become changes in all copies of the array.
I do not want to mutate the array as it creates an issue when attempting to use an array to initialize and reset the state. Once the initialization array becomes unified with the state array, the reset function will no longer work. I have made some attempts at preventing array unification without success. Below is the problem, some of my attempts to resolve the issue and a work around.
Lets say the state of the form is defined as:
const [field, setField] = useState(initialField);
Once this code executes, the value of array initialField is unified with the value of the array field.
Lets say we want to reset the form and use a function like this:
const reset = () =>{setField(initialField)}
This reset function will not work. Once the value of initialField is unified to the current value of field, setField will always contain the current value of field.
Lets say we replace initialField with:
initialField.map((e)=>{return e;})
The map method is immutable. It should create a separate array. However, replacing initialField with a map method of initialField does not change the result. useState still unifies the value of initalField with field.
Using the separator operator, {...intialField}, does not change the outcome. Neither does using intialField.concat(). Lets say we assign initalField to another array resetField. React will unify together all three arrays: field, initialField and resetField.
Lets say we hard code the array into the setState function. Assuming we have an array of N objects, it would look something like:
const [field, setField] = useState([{object 0}, {object 1} ... {object N}] );
The reset function is still:
const reset = () =>{setField(initialField)}
The reset function will work exactly once. Once the code is executed, React unifies initialField with field, so the reset function will no longer work. Lets say we replace initalField with a map method, a separator operator or a concat method. React will still unify the field array with initialField.
A work around this is to hard code the array into the setState and the reset functions as follows:
const [field, setField] = useState([{Object 0},{Object 1},...{Object N}]);
const reset = () =>{setField([{Object 0},{Object 1},...{Object N}])};
This is an ugly solution that complicates maintenance as the same changes have to be made twice.

vue.js how to not change prop for object

in my parent component, i have a filter object as empty {}...
I have a filter component, which is the child of that parent component and I got something like this:
<filter-component :filters.sync="filters" /> (filters is the {} empty from the start).
in the filter-component there's a method which does this:
filter(group, filter) {
const filters = this.filters;
// do something here as in change the filter
// filters[group].push(filter);
this.$emit('update:filters', Object.assign({}, filters));
}
as you can see, child component happens to change the prop(not directly reference, because of push in the nested one), but still, it changes it.
What do i have to do to fix this?
The only idea that comes to my mind is that the line where I have const filters = this.filters should be changed so that instead of equal, it deep copies . This way, each time filter changes from filter-component , it's gonna emit totally new one and also won't change it .
Shallow copy seems to not work in this case. Any other idea other than deep copy ? i am trying to avoid lodash package at all.
For adding properties to an existing object, you can use $set API to ensure they trigger reactive updates. In this case, you could loop through your filters and set each one.

Updating an object in the ngrx/store

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.

Firebase deep querying

This question is about implementing firebase deep querying. Consider the following structure in firebase:
Here, my ref is pointing to the root of the structure which is /messages. So I have :
var ref = new Firebase("https://cofounder.firebaseio.com/messages");
I wish to query those message Id's having member = -752163252 . So basically the returned object should be the one with key 655974744 . How do I go about doing this in firebase?
Here's what I tried in my console:
ref.orderByChild("members").equalTo(235642888).on('value', function(snap){console.log("Found ",snap.val())});
Result was:
VM837:2 Found null
I sense there is a missing link somewhere. Basically, I want to know if there is any way to query the deep nested data without having the parent key id's (in this case 25487894,655974744) .
Also, if I may extend my question, is there a way to add a listener that calls back when a new messageId (in this case 25487894,655974744) is added containing member = -752163252 .
Hope my question is clear enough. Any help is greatly appreciated!
EDIT:
I have already looked at the dinosaurs example, and that's how I tried what I tried but it didn't work.
Your query asserts that the "members" node is an integer with value 235642888, which it is not, naturally. It's an object containing a list of keys.
Instead, you would want to use the deep child query syntax and something like the following:
ref.orderByChild("members/235642888").equalTo(235642888);
Note that you don't really need the value of the key to be the key itself. You could save storage by just setting this to a binary true or 1. The query would be much the same:
ref.orderByChild("members/235642888").equalTo(true);

Categories