React : how to update states which have both mutable and immutable values - javascript

In Javascript, string, integer and boolean values are immutable, but objects and arrays are mutable.
How should we update states in React, if states have both types of values?
e.g.
constructor(props) {
super(props);
this.state = {
success: false,
error: false,
errorMessages: {}
};
}
Assuming that you need to upgdate all of the properties (success, error, errorMessages) at once, what would be best way to achieve it?
At least I'm sure that errorMessages shouldn't be updated directly, because it's mutable by nature, but what about the rest of them?
I tried something like the following, but this ends up in a wrong result.
const errorMessages = {
...this.state,
"errorMessages": error.response.data,
};
this.setState({
errorMessages,
success: false,
error: true,
});
//The errorMessages property will have "success" and "error" property in it

As long as you supply a new value for errorMessages, React will update the state correctly. You're not mutating state directly here, you're just providing a new value for the field, and React will do the necessary mutation:
this.setState({
errorMessages: error.response.data
success: false,
error: true,
});

So assuming your state is originally this
this.state = {
success: false,
error: false,
errorMessages: {}
};
And then you create a new object for your errorMessages like this
const errorMessages = {
...this.state,
"errorMessages": error.response.data,
};
this.setState({
errorMessages,
success: false,
error: true,
});
Then, your next state will kinda look like this, and I am unsure if this is what you want
{
errorMesages: {
success: false,
error: true,
errorMessages: {
// content of the error.response.data
}
},
success: false,
error: true
}
You probably wanted to assign the new state directly, which is in fact the errorMessages const you created, you are just over doing it ;)
The reason why this is so, is because when adding a variable to an object without a value, but just by name, javascript will automatically name the label the same as the variable, eg:
const a = 10;
const b = {
a
};
// result in: { a: 10 };
console.log(b);

There are 3 ways to update state:
this.setState({
success: !this.state.success,
error: !this.state.error,
errorMessages: delete this.state.id // if id were a prop in errorMessages
})
this.setState((prevState) => {
return {
success: !prevState.success,
error: !prevState.error,
errorMessages
}
});
this.setState((prevState) => {
return {
success: !prevState.success,
error: !prevState.error,
errorMessages
}
}, () => { // some callback function to execute after setState completes })

Related

spread state in react redux

I want to know what does ...state inside { ...state } do? Is it to change the value of the store to the initial value or to let the store be the latest value?
import * as actionType from "../actions/actionTypes";
const initialStore = {
roomsCount: 0,
resPerPage: 0,
rooms: [],
filteredRooms: 0,
error: null,
success: false,
};
const reducer = (state = initialStore, action) => {
switch (action.type) {
case actionType.ALL_ROOM_SUCCESS:
return {
...state,
success: true,
rooms: action.rooms,
roomsCount: action.roomsCount,
resPerPage: action.resPerPage,
filteredRooms: action.filteredRooms,
};
case actionType.ALL_ROOM_FAILED:
return {
...state,
error: action.err,
};
}
};
If at first I use this reducer, it'll be successful so success will be true and error will be null. But if it fails the 2nd time and I use ...state in this situation, what is the success value? Is it the initial value (false) or does it keep the value from the first request (true)?
That is called the spread operator and it basically allows you to "clone" the fields of one object into a new object.
In your example, { ...state, error: action.err } means "copy all fields from state, but set the field error to action.err". It's very handy for this kind of logic where you want to change a very few fields but otherwise want to keep the original data.

Setting state in React

Here is sandbox with codehttps://codesandbox.io/s/setting-react-state-mxzkf?file=/src/App.js
We have React state:
const [valid, setValid] = useState({
nameOK: true,
nameValidated: false,
nameError: false,
emailOk: true,
emailValidated: false,
emailError: false
});
An input field and validating function:
function validateName(value) {
setValid({ ...valid, nameOK: false, nameValidated: true }); //WHY THIS DOES NOT RUN???
if (value.length > 4) {
setValid({...valid, nameOK: true, nameError: false});
} else {
setValid({ ...valid, nameError: true });
}
}
I would expect to nameValidated: true be set unconditionall with every validation,but it does not happen...
Why?
As Pascal mentioned you're using the current state and thus overriding your fist change. React does not merge properties in functional component state however. The way to do it is computing new state based on the previous state
setValid(valid =>{return { ...valid, property: newValue }});

update dynamic object in redux using spread operator

I am trying to update an object in redux using spread operator but I am not being able to.
Initial state is an empty object because category is received dynamically from api call.
pages and data are both objects which i want to update using spread operator (or whatever works best)
state = {
[category]: {
pages: {
key: value(array)
},
data: {
key: value(array)
}
}
}
At my reducer I try to update it like this
return {
...state,
[category]: {
...state[category],
pages: { ...state[category].pages, pages },
data: { ...state[category].data, doctors },
total: total,
},
};
but i get "error: TypeError: Cannot read property 'pages' of undefined"
What am I doing wrong and how can I update them correctly?
Because state.category is undefined when you fetch this category for the first time. You can fix it like that:
return {
...state,
[category]: state.category
? {
...state[category],
pages: { ...state[category].pages, pages },
data: { ...state[category].data, doctors },
total: total,
}
: {
pages,
data: doctors,
total,
},
};

graphql passing dynamic data to mutation

haven't used graphql or mongodb previously. What is the proper way to pass objects for the update mutation?
Since the only other way i see to pass multiple dynamically appearing parameters is to use input type which is appears to be a bit ineffective to me (in terms of how it looks in the code, especially with bigger objects), i just pass the possible values themselves. however in this case i need to dynamically construct updateObject, which again, going to get messy for the bigger models.
for example now i did:
Mutation: {
updateHub: async (_, { id, url, ports, enabled }) => {
const query = {'_id': id};
const updateFields = {
...(url? {url: url} : null),
...(ports? {ports: ports} : null),
...(enabled? {enabled: enabled} : null)
};
const result = await HubStore.findByIdAndUpdate(query, updateFields);
return {
success: !result ? false : true,
message: 'updated',
hub: result
};
}
}
any advise on the better way to handle this?
thanks!
It appears your code could benefit from using ES6 spread syntax -- it would permit you to deal with an arbitrary number of properties from your args object without the need for serial tertiary statements.
Mutation: {
updateHub: async (_, { id, ...restArgs } ) => {
const query = {'_id': id};
const updateFields = { ...restArgs };
const result = await HubStore.findByIdAndUpdate(query, updateFields);
return {
success: !result ? false : true,
message: 'updated',
hub: result
};
}
}
If for some reason you need to explicitly set the undefined properties to null in your object, you could possibly use some a config obj and method like defaults from the lodash library as shown below:
import { defaults } from 'lodash';
const nullFill = { url: null, ports: null, enabled: null }; // include any other properties that may be needed
Mutation: {
updateHub: async (_, { id, ...restArgs } ) => {
const query = {'_id': id};
const updateFields = defaults(restArgs, nullFill);
const result = await HubStore.findByIdAndUpdate(query, updateFields);
return {
success: !result ? false : true,
message: 'updated',
hub: result
};
}
}
Also, FWIW, I would consider placing the dynamic arguments that could be potentially be updated on its own input type, such as HubInput in this case, as suggested in the graphql docs. Below I've shown how this might work with your mutation. Note that because nothing on HubInput is flagged as requird (!) you are able to pass a dynamic collection of properties to update. Also note that if you take this appraoch you will need to properly destructure your args object initially in your mutation, something like { id, input }.
input HubInput {
url: String
ports: // whatever this type is, like [String]
enabled: Boolean
// ...Anything else that might need updating
}
type UpdateHubPayload {
success: Boolean
message: String
hub: Hub // assumes you have defined a type Hub
}
updateHub(id: Int, input: HubInput!): UpdateHubPayload

Using setState in reactJs with an object always remains empty

I have this snippet that doesn't seem to be work and it is driving me insane! Can someone please point out what I have done wrong ?
getInitialState: function () {
return {
modalUser: {},
users: []
};
},
updateModalUser: function (user) {
console.log(user);
module.React.addons.update(this.state, {
modalUser: { $set: user }
});
console.log(this.state);
},
I did try doing this originally without the addons, but I had the same result. i.e. my updateModalUser looked like:
updateModalUser: function (user) {
console.log(user);
this.setState({
modalUser: user
});
console.log(this.state);
},
This output I get either way is:
Object {id: 28, fname:"fred", lname:"flinstone"…}
Object {modalUser: {}, users: []}
this.setState() is async, you need to log the state in it’s callback:
updateModalUser: function (user) {
console.log(user);
this.setState({
modalUser: user
}, function() {
console.log(this.state);
})
}
More info here: https://facebook.github.io/react/docs/component-api.html#setstate
You should use this.setState({modalUser: newObjectHere}), which is the correct way of altering a component's state.

Categories

Resources