object Object when entering a value into the input - reactjs

An error appears when entering any value
At first I used class components, but then I started redoing them for functional ones and everything broke.
In my state I get a value like this:title > title:"[object Object] and the last symbol which I entered.
Here is the code
reducer
export const postsReducer = (state = initialState, action) => {
switch (action.type) {
case CREATE_POST:
return {...state, posts: state.posts.concat(action.payload)}
default:return state
}
}
Action
export function createPost(post){
return{
type: CREATE_POST,
payload:post
}
}
and a function in a class component
this.setState(prev => ({
...prev, ...{
[event.target.name]: event.target.value
}
}))
so I converted it into a functional one. In setTitle I store the value const [title, setTitle] = useState('');
setTitle(prev => ({
...prev, ...{
[event.target.name]:event.target.value
}
}))

This depends on how you are referencing value on your input. Given your current setTitle operation, if you are referencing title like:
<input type="text" name="title" onInput={handleInput} value={title} />
The problem is that you are turning title into an object with your setTitle operation. An object with property "title" such as { title: "some text" }. That then get's stringified into [object Object].
You could change setTitle to the following to keep it as a flat string:
setTitle(e.target.value)
Or you could change the structure of your state to be an object of form properties:
// create an object with properties to hold your form values
const [form, setForm] = useState({ title: '' });
function handleInput(e) {
setForm(prev => ({
...prev,
[e.target.name]: e.target.value,
}));
}
// reference specific property on form state object
<input type="text" onInput={handleInput} value={form.title} />
Hopefully that helps!

Related

Update Object Key Value with onClick [duplicate]

Is it at all possible to update object's properties with setState?
Something like:
this.state = {
jasper: { name: 'jasper', age: 28 },
}
I have tried:
this.setState({jasper.name: 'someOtherName'});
and this:
this.setState({jasper: {name: 'someothername'}})
The first results in a syntax error and the second just does nothing. Any ideas?
There are multiple ways of doing this, since state update is a async operation, so to update the state object, we need to use updater function with setState.
1- Simplest one:
First create a copy of jasper then do the changes in that:
this.setState(prevState => {
let jasper = Object.assign({}, prevState.jasper); // creating copy of state variable jasper
jasper.name = 'someothername'; // update the name property, assign a new value
return { jasper }; // return new object jasper object
})
Instead of using Object.assign we can also write it like this:
let jasper = { ...prevState.jasper };
2- Using spread syntax:
this.setState(prevState => ({
jasper: { // object that we want to update
...prevState.jasper, // keep all other key-value pairs
name: 'something' // update the value of specific key
}
}))
Note: Object.assign and Spread Operator creates only shallow copy, so if you have defined nested object or array of objects, you need a different approach.
Updating nested state object:
Assume you have defined state as:
this.state = {
food: {
sandwich: {
capsicum: true,
crackers: true,
mayonnaise: true
},
pizza: {
jalapeno: true,
extraCheese: false
}
}
}
To update extraCheese of pizza object:
this.setState(prevState => ({
food: {
...prevState.food, // copy all other key-value pairs of food object
pizza: { // specific object of food object
...prevState.food.pizza, // copy all pizza key-value pairs
extraCheese: true // update value of specific key
}
}
}))
Updating array of objects:
Lets assume you have a todo app, and you are managing the data in this form:
this.state = {
todoItems: [
{
name: 'Learn React Basics',
status: 'pending'
}, {
name: 'Check Codebase',
status: 'pending'
}
]
}
To update the status of any todo object, run a map on the array and check for some unique value of each object, in case of condition=true, return the new object with updated value, else same object.
let key = 2;
this.setState(prevState => ({
todoItems: prevState.todoItems.map(
el => el.key === key? { ...el, status: 'done' }: el
)
}))
Suggestion: If object doesn't have a unique value, then use array index.
This is the fastest and the most readable way:
this.setState({...this.state.jasper, name: 'someothername'});
Even if this.state.jasper already contains a name property, the new name name: 'someothername' with be used.
Use spread operator and some ES6 here
this.setState({
jasper: {
...this.state.jasper,
name: 'something'
}
})
I know there are a lot of answers here, but I'm surprised none of them create a copy of the new object outside of setState, and then simply setState({newObject}). Clean, concise and reliable. So in this case:
const jasper = { ...this.state.jasper, name: 'someothername' }
this.setState(() => ({ jasper }))
Or for a dynamic property (very useful for forms)
const jasper = { ...this.state.jasper, [VarRepresentingPropertyName]: 'new value' }
this.setState(() => ({ jasper }))
Using hook we can do following way
const [student, setStudent] = React.useState({name: 'jasper', age: 28});
setStudent((prevState) => ({
...prevState,
name: 'newName',
}));
I used this solution.
If you have a nested state like this:
this.state = {
formInputs:{
friendName:{
value:'',
isValid:false,
errorMsg:''
},
friendEmail:{
value:'',
isValid:false,
errorMsg:''
}
}
}
you can declare the handleChange function that copy current status and re-assigns it with changed values
handleChange(el) {
let inputName = el.target.name;
let inputValue = el.target.value;
let statusCopy = Object.assign({}, this.state);
statusCopy.formInputs[inputName].value = inputValue;
this.setState(statusCopy);
}
here the html with the event listener. Make sure to use the same name used into state object (in this case 'friendName')
<input type="text" onChange={this.handleChange} " name="friendName" />
try this,it should work fine
this.setState(Object.assign(this.state.jasper,{name:'someOtherName'}));
Create a state object
this.state = {
objName: {
propertyOne: "",
propertyTwo: ""
}
};
Update state using setState
this.setState(prevState => ({
objName: {
...prevState.objName,
propertyOne: "Updated Value",
propertyTwo: "Updated value"
}
}));
this is another solution using immer immutabe utility, very suited for deeply nested objects with ease, and you should not care about mutation
this.setState(
produce(draft => {
draft.jasper.name = 'someothername'
})
)
Using hooks in Functional Component:
const [state, setState] = useState({jasper: { name: 'jasper', age: 28 }})
const nameChangeHandler = () => {
setState(prevState => ({
...prevState,
prevState.jasper.name = "Anurag",
prevState.jasper.age = 28
})
)
}
In these cases It is recommended to use callback-based approach to update the state , because using this approach it is ensured that previously states are fully updated and we're updating based on previously updated state.
The first case is indeed a syntax error.
Since I can't see the rest of your component, it's hard to see why you're nesting objects in your state here. It's not a good idea to nest objects in component state. Try setting your initial state to be:
this.state = {
name: 'jasper',
age: 28
}
That way, if you want to update the name, you can just call:
this.setState({
name: 'Sean'
});
Will that achieve what you're aiming for?
For larger, more complex data stores, I would use something like Redux. But that's much more advanced.
The general rule with component state is to use it only to manage UI state of the component (e.g. active, timers, etc.)
Check out these references:
https://facebook.github.io/react/docs/react-component.html#state
https://facebook.github.io/react/docs/state-and-lifecycle.html
In case of updating an object where keys are string
e.g. let say your state object is
serviceDays: {
Sunday: true,
Monday: true,
Tuesday: false,
Wednesday: true,
Thurday: false,
Friday: true,
Saturday: true
}
so you can update in following way
const onDayClick = day => {
const { serviceDays } = this.state
this.setState(prevState => ({
serviceDays: {
...prevState.serviceDays,
[day]: serviceDays[day] ? false : true
}
}))
}
Another option: define your variable out of the Jasper object and then just call a variable.
Spread operator: ES6
this.state = { jasper: { name: 'jasper', age: 28 } }
let foo = "something that needs to be saved into state"
this.setState(prevState => ({
jasper: {
...jasper.entity,
foo
}
})
You can try with this:
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState.name = 'someOtherName';
return {jasper: prevState}
})
or for other property:
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState.age = 'someOtherAge';
return {jasper: prevState}
})
Or you can use handleChage function:
handleChage(event) {
const {name, value} = event.target;
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState[name] = value;
return {jasper: prevState}
})
}
and HTML code:
<input
type={"text"}
name={"name"}
value={this.state.jasper.name}
onChange={this.handleChange}
/>
<br/>
<input
type={"text"}
name={"age"}
value={this.state.jasper.age}
onChange={this.handleChange}
/>
You can try with this:
(Note: name of input tag === field of object)
<input name="myField" type="text"
value={this.state.myObject.myField}
onChange={this.handleChangeInpForm}>
</input>
-----------------------------------------------------------
handleChangeInpForm = (e) => {
let newObject = this.state.myObject;
newObject[e.target.name] = e.target.value;
this.setState({
myObject: newObject
})
}
Simple and dynamic way.
This will do the job, but you need to set all the ids to the parent so the parent will point to the name of the object, being id = "jasper" and name the name of the input element = property inside of the object jasper.
handleChangeObj = ({target: { id , name , value}}) => this.setState({ [id]: { ...this.state[id] , [name]: value } });
Without using Async and Await Use this...
funCall(){
this.setState({...this.state.jasper, name: 'someothername'});
}
If you using with Async And Await use this...
async funCall(){
await this.setState({...this.state.jasper, name: 'someothername'});
}
Also, following Alberto Piras solution, if you don't want to copy all the "state" object:
handleChange(el) {
let inputName = el.target.name;
let inputValue = el.target.value;
let jasperCopy = Object.assign({}, this.state.jasper);
jasperCopy[inputName].name = inputValue;
this.setState({jasper: jasperCopy});
}
Try with this:
const { jasper } = this.state; //Gets the object from state
jasper.name = 'A new name'; //do whatever you want with the object
this.setState({jasper}); //Replace the object in state
By using the input html input name attribute we can have a more dynamic approach in order to update an object properties.
DOM
html input name attribute
<input type="text" name="fname" handleChange={(e: any) => { updatePerson(e) }}/>
<input type="text" name="lname" handleChange={(e: any) => { updatePerson(e) }}/>
React / TSX
object.assign
const [person, setPerson] = useState<IPerson>({});
function updatePerson(e: React.ChangeEvent<HTMLInputElement>): void {
const { name, value } = e.currentTarget;
setPerson(prevState => {
const newState = Object.assign(person, { [name]: value })
return { ...prevState, ...newState };
});
}
Sample FC:
const [formData, setformData] = useState({
project_admin_permissions: {
task_forms: false,
auto_assign_rules: false,
project_notes: true,
alerts: false,
update_criteria: true,
project_flow: false,
reports: false,
}
})
const handleChangeCheckBox = (e) => {
setformData({
...formData, project_admin_permissions: { ...formData.project_admin_permissions, [e.target.name]: e.target.checked }
})
}
This setup worked for me:
let newState = this.state.jasper;
newState.name = 'someOtherName';
this.setState({newState: newState});
console.log(this.state.jasper.name); //someOtherName
Your second approach doesn't work because {name: 'someothername'} equals {name: 'someothername', age: undefined}, so theundefined would overwrite original age value.
When it comes to change state in nested objects, a good approach would be Immutable.js
this.state = {
jasper: Record({name: 'jasper', age: 28})
}
const {jasper} = this.state
this.setState({jasper: jasper.set(name, 'someothername')})

React useState with Object with multiple boolean fields

I have this object initialised with useState:
const [
emailNotifications,
setEmailNotifications,
] = useState<emailNotifications>({
rating: false,
favourites: false,
payments: false,
refunds: false,
sales: false,
});
And I created a function that should dynamically change the value for each field but I am struggling with assigning the opposite boolean value onClick. This is the function:
const handleEmailNotificationsSettings = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setEmailNotifications({
...emailNotifications,
[event.target.id]: !event.target.id,
});
};
What am I doing wrong?
Your approach is right just one minor thing that you trying to achieve here is wrong.
setEmailNotifications({
...emailNotifications,
[event.target.id]: !event.target.id, //Here
});
when you are setting dynamic value to the state you are expecting it to be the Boolean value which is not
solution:
setEmailNotifications({
...emailNotifications,
[event.target.id]: !emailNotifications[event.target.id],
});
#kunal panchal's answer is totally valid Javascript, but it does cause a Typescript error because the type of event.target.id is string so Typescript does not know for sure that it's a valid key of emailNotifications. You have to assert that it is correct by using as.
!emailNotifications[event.target.id as keyof EmailNotifications]
One way to avoid this is to get the boolean value by looking at the checked property on the input rather than toggling the state.
As a sidenote, it's a good best practice to get the current state by using a setState callback so that you always get the correct value if multiple updates are batched together.
const _handleEmailNotificationsSettings = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setEmailNotifications(prevState => ({
...prevState,
[event.target.id]: event.target.checked,
}));
};
This is probably the best solution for a checkbox.
Another approach which is more flexible to other situations is to use a curried function. Instead of getting the property from the event.target.id, where it will always be string, we pass the property as an argument to create an individual handler for each property.
const handleEmailNotificationsSettings = (
property: keyof EmailNotifications
) => () => {
setEmailNotifications((prevState) => ({
...prevState,
[property]: !emailNotifications[property]
}));
};
or
const handleEmailNotificationsSettings = (
property: keyof EmailNotifications
) => (event: React.ChangeEvent<HTMLInputElement>) => {
setEmailNotifications((prevState) => ({
...prevState,
[property]: event.target.checked
}));
};
which you use like this:
<input
type="checkbox"
checked={emailNotifications.favourites}
onChange={handleEmailNotificationsSettings("favourites")}
/>
Those solutions avoid having to make as assertions in the event handler, but sometimes they are inevitable. I am looping through your state using (Object.keys(emailNotifications) and I need to make an assertion there because Object.keys always returns string[].
import React, { useState } from "react";
// I am defining this separately so that I can use typeof to extract the type
// you don't need to do this if you have the type defined elsewhere
const initialNotifications = {
rating: false,
favourites: false,
payments: false,
refunds: false,
sales: false
};
type EmailNotifications = typeof initialNotifications;
const MyComponent = () => {
// you don't really need to declare the type when you have an initial value
const [emailNotifications, setEmailNotifications] = useState(
initialNotifications
);
const handleEmailNotificationsSettings = (
property: keyof EmailNotifications
) => (event: React.ChangeEvent<HTMLInputElement>) => {
setEmailNotifications((prevState) => ({
...prevState,
[property]: event.target.checked
}));
};
return (
<div>
{(Object.keys(emailNotifications) as Array<keyof EmailNotifications>).map(
(property) => (
<div key={property}>
<label>
<input
type="checkbox"
id={property}
checked={emailNotifications[property]}
onChange={handleEmailNotificationsSettings(property)}
/>
{property}
</label>
</div>
)
)}
</div>
);
};
export default MyComponent;

How react update specific state inner state object? [duplicate]

Is it at all possible to update object's properties with setState?
Something like:
this.state = {
jasper: { name: 'jasper', age: 28 },
}
I have tried:
this.setState({jasper.name: 'someOtherName'});
and this:
this.setState({jasper: {name: 'someothername'}})
The first results in a syntax error and the second just does nothing. Any ideas?
There are multiple ways of doing this, since state update is a async operation, so to update the state object, we need to use updater function with setState.
1- Simplest one:
First create a copy of jasper then do the changes in that:
this.setState(prevState => {
let jasper = Object.assign({}, prevState.jasper); // creating copy of state variable jasper
jasper.name = 'someothername'; // update the name property, assign a new value
return { jasper }; // return new object jasper object
})
Instead of using Object.assign we can also write it like this:
let jasper = { ...prevState.jasper };
2- Using spread syntax:
this.setState(prevState => ({
jasper: { // object that we want to update
...prevState.jasper, // keep all other key-value pairs
name: 'something' // update the value of specific key
}
}))
Note: Object.assign and Spread Operator creates only shallow copy, so if you have defined nested object or array of objects, you need a different approach.
Updating nested state object:
Assume you have defined state as:
this.state = {
food: {
sandwich: {
capsicum: true,
crackers: true,
mayonnaise: true
},
pizza: {
jalapeno: true,
extraCheese: false
}
}
}
To update extraCheese of pizza object:
this.setState(prevState => ({
food: {
...prevState.food, // copy all other key-value pairs of food object
pizza: { // specific object of food object
...prevState.food.pizza, // copy all pizza key-value pairs
extraCheese: true // update value of specific key
}
}
}))
Updating array of objects:
Lets assume you have a todo app, and you are managing the data in this form:
this.state = {
todoItems: [
{
name: 'Learn React Basics',
status: 'pending'
}, {
name: 'Check Codebase',
status: 'pending'
}
]
}
To update the status of any todo object, run a map on the array and check for some unique value of each object, in case of condition=true, return the new object with updated value, else same object.
let key = 2;
this.setState(prevState => ({
todoItems: prevState.todoItems.map(
el => el.key === key? { ...el, status: 'done' }: el
)
}))
Suggestion: If object doesn't have a unique value, then use array index.
This is the fastest and the most readable way:
this.setState({...this.state.jasper, name: 'someothername'});
Even if this.state.jasper already contains a name property, the new name name: 'someothername' with be used.
Use spread operator and some ES6 here
this.setState({
jasper: {
...this.state.jasper,
name: 'something'
}
})
I know there are a lot of answers here, but I'm surprised none of them create a copy of the new object outside of setState, and then simply setState({newObject}). Clean, concise and reliable. So in this case:
const jasper = { ...this.state.jasper, name: 'someothername' }
this.setState(() => ({ jasper }))
Or for a dynamic property (very useful for forms)
const jasper = { ...this.state.jasper, [VarRepresentingPropertyName]: 'new value' }
this.setState(() => ({ jasper }))
Using hook we can do following way
const [student, setStudent] = React.useState({name: 'jasper', age: 28});
setStudent((prevState) => ({
...prevState,
name: 'newName',
}));
I used this solution.
If you have a nested state like this:
this.state = {
formInputs:{
friendName:{
value:'',
isValid:false,
errorMsg:''
},
friendEmail:{
value:'',
isValid:false,
errorMsg:''
}
}
}
you can declare the handleChange function that copy current status and re-assigns it with changed values
handleChange(el) {
let inputName = el.target.name;
let inputValue = el.target.value;
let statusCopy = Object.assign({}, this.state);
statusCopy.formInputs[inputName].value = inputValue;
this.setState(statusCopy);
}
here the html with the event listener. Make sure to use the same name used into state object (in this case 'friendName')
<input type="text" onChange={this.handleChange} " name="friendName" />
try this,it should work fine
this.setState(Object.assign(this.state.jasper,{name:'someOtherName'}));
Create a state object
this.state = {
objName: {
propertyOne: "",
propertyTwo: ""
}
};
Update state using setState
this.setState(prevState => ({
objName: {
...prevState.objName,
propertyOne: "Updated Value",
propertyTwo: "Updated value"
}
}));
this is another solution using immer immutabe utility, very suited for deeply nested objects with ease, and you should not care about mutation
this.setState(
produce(draft => {
draft.jasper.name = 'someothername'
})
)
Using hooks in Functional Component:
const [state, setState] = useState({jasper: { name: 'jasper', age: 28 }})
const nameChangeHandler = () => {
setState(prevState => ({
...prevState,
prevState.jasper.name = "Anurag",
prevState.jasper.age = 28
})
)
}
In these cases It is recommended to use callback-based approach to update the state , because using this approach it is ensured that previously states are fully updated and we're updating based on previously updated state.
The first case is indeed a syntax error.
Since I can't see the rest of your component, it's hard to see why you're nesting objects in your state here. It's not a good idea to nest objects in component state. Try setting your initial state to be:
this.state = {
name: 'jasper',
age: 28
}
That way, if you want to update the name, you can just call:
this.setState({
name: 'Sean'
});
Will that achieve what you're aiming for?
For larger, more complex data stores, I would use something like Redux. But that's much more advanced.
The general rule with component state is to use it only to manage UI state of the component (e.g. active, timers, etc.)
Check out these references:
https://facebook.github.io/react/docs/react-component.html#state
https://facebook.github.io/react/docs/state-and-lifecycle.html
In case of updating an object where keys are string
e.g. let say your state object is
serviceDays: {
Sunday: true,
Monday: true,
Tuesday: false,
Wednesday: true,
Thurday: false,
Friday: true,
Saturday: true
}
so you can update in following way
const onDayClick = day => {
const { serviceDays } = this.state
this.setState(prevState => ({
serviceDays: {
...prevState.serviceDays,
[day]: serviceDays[day] ? false : true
}
}))
}
Another option: define your variable out of the Jasper object and then just call a variable.
Spread operator: ES6
this.state = { jasper: { name: 'jasper', age: 28 } }
let foo = "something that needs to be saved into state"
this.setState(prevState => ({
jasper: {
...jasper.entity,
foo
}
})
You can try with this:
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState.name = 'someOtherName';
return {jasper: prevState}
})
or for other property:
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState.age = 'someOtherAge';
return {jasper: prevState}
})
Or you can use handleChage function:
handleChage(event) {
const {name, value} = event.target;
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState[name] = value;
return {jasper: prevState}
})
}
and HTML code:
<input
type={"text"}
name={"name"}
value={this.state.jasper.name}
onChange={this.handleChange}
/>
<br/>
<input
type={"text"}
name={"age"}
value={this.state.jasper.age}
onChange={this.handleChange}
/>
You can try with this:
(Note: name of input tag === field of object)
<input name="myField" type="text"
value={this.state.myObject.myField}
onChange={this.handleChangeInpForm}>
</input>
-----------------------------------------------------------
handleChangeInpForm = (e) => {
let newObject = this.state.myObject;
newObject[e.target.name] = e.target.value;
this.setState({
myObject: newObject
})
}
Simple and dynamic way.
This will do the job, but you need to set all the ids to the parent so the parent will point to the name of the object, being id = "jasper" and name the name of the input element = property inside of the object jasper.
handleChangeObj = ({target: { id , name , value}}) => this.setState({ [id]: { ...this.state[id] , [name]: value } });
Without using Async and Await Use this...
funCall(){
this.setState({...this.state.jasper, name: 'someothername'});
}
If you using with Async And Await use this...
async funCall(){
await this.setState({...this.state.jasper, name: 'someothername'});
}
Also, following Alberto Piras solution, if you don't want to copy all the "state" object:
handleChange(el) {
let inputName = el.target.name;
let inputValue = el.target.value;
let jasperCopy = Object.assign({}, this.state.jasper);
jasperCopy[inputName].name = inputValue;
this.setState({jasper: jasperCopy});
}
Try with this:
const { jasper } = this.state; //Gets the object from state
jasper.name = 'A new name'; //do whatever you want with the object
this.setState({jasper}); //Replace the object in state
By using the input html input name attribute we can have a more dynamic approach in order to update an object properties.
DOM
html input name attribute
<input type="text" name="fname" handleChange={(e: any) => { updatePerson(e) }}/>
<input type="text" name="lname" handleChange={(e: any) => { updatePerson(e) }}/>
React / TSX
object.assign
const [person, setPerson] = useState<IPerson>({});
function updatePerson(e: React.ChangeEvent<HTMLInputElement>): void {
const { name, value } = e.currentTarget;
setPerson(prevState => {
const newState = Object.assign(person, { [name]: value })
return { ...prevState, ...newState };
});
}
Sample FC:
const [formData, setformData] = useState({
project_admin_permissions: {
task_forms: false,
auto_assign_rules: false,
project_notes: true,
alerts: false,
update_criteria: true,
project_flow: false,
reports: false,
}
})
const handleChangeCheckBox = (e) => {
setformData({
...formData, project_admin_permissions: { ...formData.project_admin_permissions, [e.target.name]: e.target.checked }
})
}
This setup worked for me:
let newState = this.state.jasper;
newState.name = 'someOtherName';
this.setState({newState: newState});
console.log(this.state.jasper.name); //someOtherName
Your second approach doesn't work because {name: 'someothername'} equals {name: 'someothername', age: undefined}, so theundefined would overwrite original age value.
When it comes to change state in nested objects, a good approach would be Immutable.js
this.state = {
jasper: Record({name: 'jasper', age: 28})
}
const {jasper} = this.state
this.setState({jasper: jasper.set(name, 'someothername')})

Simple setState on object using hooks

const [state, setState] = useState({ city: '', country: '' });
const handleCityChange = event => {
setState(prevState => {
console.log(prevState);
return { ...prevState, city: event.target.value };
});
};
//...
<input
type="text"
placeholder="city"
value={state.city}
onChange={handleCityChange}
/>
I'm trying to destructuring prevState, which is an object, and update only the city property. On first keystroke, it's working fine without error, but as soon as I'm typing second letter, i will hit error
Uncaught TypeError: Cannot read property 'value' of null
May I know which part is the null coming from? And from chrome console I see below warning not sure if it's related
Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're accessing the property target on a released/nullified synthetic event. This is set to null. If you must keep the original synthetic event around, use event.persist(). See https:...//react-event-pooling for more information.
UPDATES:
If we're not using setState functionally, then it's working fine?
const handleCityChange = event => {
setState({ ...state, city: event.target.value });
};
An event has to be handled synchronously in React. You can extract the value from the target before you call setState:
const handleCityChange = event => {
const { value } = event.target;
setState(prevState => {
return { ...prevState, city: value };
});
};
Another way of going about it is to persist the event, and it can be used asynchronously.
const handleCityChange = event => {
event.persist();
setState(prevState => {
return { ...prevState, city: event.target.value };
});
};

Updating an object with setState in React

Is it at all possible to update object's properties with setState?
Something like:
this.state = {
jasper: { name: 'jasper', age: 28 },
}
I have tried:
this.setState({jasper.name: 'someOtherName'});
and this:
this.setState({jasper: {name: 'someothername'}})
The first results in a syntax error and the second just does nothing. Any ideas?
There are multiple ways of doing this, since state update is a async operation, so to update the state object, we need to use updater function with setState.
1- Simplest one:
First create a copy of jasper then do the changes in that:
this.setState(prevState => {
let jasper = Object.assign({}, prevState.jasper); // creating copy of state variable jasper
jasper.name = 'someothername'; // update the name property, assign a new value
return { jasper }; // return new object jasper object
})
Instead of using Object.assign we can also write it like this:
let jasper = { ...prevState.jasper };
2- Using spread syntax:
this.setState(prevState => ({
jasper: { // object that we want to update
...prevState.jasper, // keep all other key-value pairs
name: 'something' // update the value of specific key
}
}))
Note: Object.assign and Spread Operator creates only shallow copy, so if you have defined nested object or array of objects, you need a different approach.
Updating nested state object:
Assume you have defined state as:
this.state = {
food: {
sandwich: {
capsicum: true,
crackers: true,
mayonnaise: true
},
pizza: {
jalapeno: true,
extraCheese: false
}
}
}
To update extraCheese of pizza object:
this.setState(prevState => ({
food: {
...prevState.food, // copy all other key-value pairs of food object
pizza: { // specific object of food object
...prevState.food.pizza, // copy all pizza key-value pairs
extraCheese: true // update value of specific key
}
}
}))
Updating array of objects:
Lets assume you have a todo app, and you are managing the data in this form:
this.state = {
todoItems: [
{
name: 'Learn React Basics',
status: 'pending'
}, {
name: 'Check Codebase',
status: 'pending'
}
]
}
To update the status of any todo object, run a map on the array and check for some unique value of each object, in case of condition=true, return the new object with updated value, else same object.
let key = 2;
this.setState(prevState => ({
todoItems: prevState.todoItems.map(
el => el.key === key? { ...el, status: 'done' }: el
)
}))
Suggestion: If object doesn't have a unique value, then use array index.
This is the fastest and the most readable way:
this.setState({...this.state.jasper, name: 'someothername'});
Even if this.state.jasper already contains a name property, the new name name: 'someothername' with be used.
Use spread operator and some ES6 here
this.setState({
jasper: {
...this.state.jasper,
name: 'something'
}
})
I know there are a lot of answers here, but I'm surprised none of them create a copy of the new object outside of setState, and then simply setState({newObject}). Clean, concise and reliable. So in this case:
const jasper = { ...this.state.jasper, name: 'someothername' }
this.setState(() => ({ jasper }))
Or for a dynamic property (very useful for forms)
const jasper = { ...this.state.jasper, [VarRepresentingPropertyName]: 'new value' }
this.setState(() => ({ jasper }))
Using hook we can do following way
const [student, setStudent] = React.useState({name: 'jasper', age: 28});
setStudent((prevState) => ({
...prevState,
name: 'newName',
}));
I used this solution.
If you have a nested state like this:
this.state = {
formInputs:{
friendName:{
value:'',
isValid:false,
errorMsg:''
},
friendEmail:{
value:'',
isValid:false,
errorMsg:''
}
}
}
you can declare the handleChange function that copy current status and re-assigns it with changed values
handleChange(el) {
let inputName = el.target.name;
let inputValue = el.target.value;
let statusCopy = Object.assign({}, this.state);
statusCopy.formInputs[inputName].value = inputValue;
this.setState(statusCopy);
}
here the html with the event listener. Make sure to use the same name used into state object (in this case 'friendName')
<input type="text" onChange={this.handleChange} " name="friendName" />
try this,it should work fine
this.setState(Object.assign(this.state.jasper,{name:'someOtherName'}));
Create a state object
this.state = {
objName: {
propertyOne: "",
propertyTwo: ""
}
};
Update state using setState
this.setState(prevState => ({
objName: {
...prevState.objName,
propertyOne: "Updated Value",
propertyTwo: "Updated value"
}
}));
this is another solution using immer immutabe utility, very suited for deeply nested objects with ease, and you should not care about mutation
this.setState(
produce(draft => {
draft.jasper.name = 'someothername'
})
)
Using hooks in Functional Component:
const [state, setState] = useState({jasper: { name: 'jasper', age: 28 }})
const nameChangeHandler = () => {
setState(prevState => ({
...prevState,
prevState.jasper.name = "Anurag",
prevState.jasper.age = 28
})
)
}
In these cases It is recommended to use callback-based approach to update the state , because using this approach it is ensured that previously states are fully updated and we're updating based on previously updated state.
The first case is indeed a syntax error.
Since I can't see the rest of your component, it's hard to see why you're nesting objects in your state here. It's not a good idea to nest objects in component state. Try setting your initial state to be:
this.state = {
name: 'jasper',
age: 28
}
That way, if you want to update the name, you can just call:
this.setState({
name: 'Sean'
});
Will that achieve what you're aiming for?
For larger, more complex data stores, I would use something like Redux. But that's much more advanced.
The general rule with component state is to use it only to manage UI state of the component (e.g. active, timers, etc.)
Check out these references:
https://facebook.github.io/react/docs/react-component.html#state
https://facebook.github.io/react/docs/state-and-lifecycle.html
In case of updating an object where keys are string
e.g. let say your state object is
serviceDays: {
Sunday: true,
Monday: true,
Tuesday: false,
Wednesday: true,
Thurday: false,
Friday: true,
Saturday: true
}
so you can update in following way
const onDayClick = day => {
const { serviceDays } = this.state
this.setState(prevState => ({
serviceDays: {
...prevState.serviceDays,
[day]: serviceDays[day] ? false : true
}
}))
}
Another option: define your variable out of the Jasper object and then just call a variable.
Spread operator: ES6
this.state = { jasper: { name: 'jasper', age: 28 } }
let foo = "something that needs to be saved into state"
this.setState(prevState => ({
jasper: {
...jasper.entity,
foo
}
})
You can try with this:
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState.name = 'someOtherName';
return {jasper: prevState}
})
or for other property:
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState.age = 'someOtherAge';
return {jasper: prevState}
})
Or you can use handleChage function:
handleChage(event) {
const {name, value} = event.target;
this.setState(prevState => {
prevState = JSON.parse(JSON.stringify(this.state.jasper));
prevState[name] = value;
return {jasper: prevState}
})
}
and HTML code:
<input
type={"text"}
name={"name"}
value={this.state.jasper.name}
onChange={this.handleChange}
/>
<br/>
<input
type={"text"}
name={"age"}
value={this.state.jasper.age}
onChange={this.handleChange}
/>
You can try with this:
(Note: name of input tag === field of object)
<input name="myField" type="text"
value={this.state.myObject.myField}
onChange={this.handleChangeInpForm}>
</input>
-----------------------------------------------------------
handleChangeInpForm = (e) => {
let newObject = this.state.myObject;
newObject[e.target.name] = e.target.value;
this.setState({
myObject: newObject
})
}
Simple and dynamic way.
This will do the job, but you need to set all the ids to the parent so the parent will point to the name of the object, being id = "jasper" and name the name of the input element = property inside of the object jasper.
handleChangeObj = ({target: { id , name , value}}) => this.setState({ [id]: { ...this.state[id] , [name]: value } });
Without using Async and Await Use this...
funCall(){
this.setState({...this.state.jasper, name: 'someothername'});
}
If you using with Async And Await use this...
async funCall(){
await this.setState({...this.state.jasper, name: 'someothername'});
}
Also, following Alberto Piras solution, if you don't want to copy all the "state" object:
handleChange(el) {
let inputName = el.target.name;
let inputValue = el.target.value;
let jasperCopy = Object.assign({}, this.state.jasper);
jasperCopy[inputName].name = inputValue;
this.setState({jasper: jasperCopy});
}
Try with this:
const { jasper } = this.state; //Gets the object from state
jasper.name = 'A new name'; //do whatever you want with the object
this.setState({jasper}); //Replace the object in state
By using the input html input name attribute we can have a more dynamic approach in order to update an object properties.
DOM
html input name attribute
<input type="text" name="fname" handleChange={(e: any) => { updatePerson(e) }}/>
<input type="text" name="lname" handleChange={(e: any) => { updatePerson(e) }}/>
React / TSX
object.assign
const [person, setPerson] = useState<IPerson>({});
function updatePerson(e: React.ChangeEvent<HTMLInputElement>): void {
const { name, value } = e.currentTarget;
setPerson(prevState => {
const newState = Object.assign(person, { [name]: value })
return { ...prevState, ...newState };
});
}
Sample FC:
const [formData, setformData] = useState({
project_admin_permissions: {
task_forms: false,
auto_assign_rules: false,
project_notes: true,
alerts: false,
update_criteria: true,
project_flow: false,
reports: false,
}
})
const handleChangeCheckBox = (e) => {
setformData({
...formData, project_admin_permissions: { ...formData.project_admin_permissions, [e.target.name]: e.target.checked }
})
}
This setup worked for me:
let newState = this.state.jasper;
newState.name = 'someOtherName';
this.setState({newState: newState});
console.log(this.state.jasper.name); //someOtherName
Your second approach doesn't work because {name: 'someothername'} equals {name: 'someothername', age: undefined}, so theundefined would overwrite original age value.
When it comes to change state in nested objects, a good approach would be Immutable.js
this.state = {
jasper: Record({name: 'jasper', age: 28})
}
const {jasper} = this.state
this.setState({jasper: jasper.set(name, 'someothername')})

Resources