DEV Community

loading...

Updating react nested state properties

Wale Ayandiran
Software Engineer & Mentor
Updated on ・2 min read

alt-text

Working with react inevitable requires you to manage the state of your application at some point, bearest minimum the state of a component you're currently working with.

Recently, I was working on a side project, and I felt like trying something different from the regular

this.state = { email: '', password: '' };

I was trying to login a user with email and password credential, so I felt like wrapping that up into a user object for human readability or better still no reason sake :).

I did this

this.state = {
  user: {
    email: '',
    password: ''
  }
};

Then i noticed my form fields were not taking in the inputs, more like its on readOnly mode.

After googling, it occured to me that the state wasn't updating using the regular

 onChange(e) {
   const { name, value } = e.target;
   this.setState({ [name]: value });

Why because the properties (email and password) are nested under the user state property. At first, I was like that shouldn't be much of a problem, calling user.email, user.password should do the trick right? You guessed right, it didn't and there i was thinking straight i just wanted things to work so I can move on wiht my code.

The Solution

Quick answer:

onChange(e) { 
  const { user } = { ...this.state };
  const currentState = user;
  const { name, value } = e.target;
  currentState[name] = value;

  this.setState({ user: currentState });
}

Long Version:

react's setState doesn't take care of nested properties, in this case email and password. So the only way to make changes is to access the parent state object user whenever a new change occurs to either email or password.

What this means is that everytime you type a new character on any of the field, the user state gets to be re-created. This is not optimal if your user object has lots of fields say like a bulky registration form dealing with over 10 fields is not considered ideal.

The above code uses some es6 features, such as the spread operator & Destructuring.

It's a general bad practise to mutate (add/remove elements of the state directly) state in react. states should be recreated if it must change.

The line const { user } = { ...this.state } does just that using the spread operator(...) to get the current state. Assigned the value returned to currentState variable const currentState = user;, destructure the emmitted events const { name, value } = e.target; coming from the input fields to get the name and thier value. Update the currentState with the value of the input currentState[name] = value; where name is the property of the user state object email and password.

Finally, we transpose the user with the currentState whenever an update has been successfully made this.setState({ user: currentState });.

My recommendation

Some people will argue about using nested properties because React is not oriented to work with nested state. Maybe this is correct or not but here's what I think;

I would suggest you avoid using nested state, if you must then endeavor to make it as light as possible because the downside is every tiny piece of change will recreate the parent object, which is not good for performance.

Discussion (8)

Collapse
bryanspearman profile image
Bryan Spearman

I've been searching high and low for a solution to this issue and your article hits the nail on the head. I'll also stray from nested state for performance reasons as my app has dozens of forms and dozens of fields per form.

It does seem a bit awkward that state would not support nested state more easily. This should seriously be a consideration in future releases.

Collapse
evankapantais profile image
Evan Kapantais

This is spot on. I am new to React and I tried doing it like this and it also seems to work:

  handleChange = (event) => {
    const target = event.target;

    if (target.id === "symbol") {
      this.setState({
        input: {
          symbol: target.value,
          amountHeld: this.state.input.amountHeld
        }
      });
    } else {
      this.setState({
        input: {
          symbol: this.state.input.symbol,
          amountHeld: target.value,
        }
      });
    }

    console.log(this.state);
  }
Collapse
avkonst profile image
Andrey

React hooks made state management a lot easier, however handling of updates for arrays, objects and nested data still requires a bit of manipulation with data. Check github.com/avkonst/react-use-state-x which makes array, object, nested data state and global state management simple and efficient using React hooks. Disclaimer: I am an author of the lib.

Collapse
_odagled_ profile image
Un tal ahí
  • this is my initialState

const initialStateInput = {
cabeceraFamilia: {
familia: '',
direccion: '',
telefonos: '',
email: ''
},
motivoConsulta: '',
fechaHora: '',
corresponsables: [],
}

  • The hook or you can replace it with de state class

const [infoAgendamiento, setInfoAgendamiento] = useState(initialStateInput);

  • The method for handleChange

const actualizarState = e => {
const nameObjects = e.target.name.split('.');
const newState = setStateNested(infoAgendamiento, nameObjects, e.target.value);
setInfoAgendamiento({...newState});
};

  • Method for set state with nested states

const setStateNested = (state, nameObjects, value) => {
let i = 0;
let operativeState = state;
if(nameObjects.length > 1){
for (i = 0; i < nameObjects.length - 1; i++) {
operativeState = operativeState[nameObjects[i]];
}
}
operativeState[nameObjects[i]] = value;
return state;
}

  • Finally this is the input that i use

< input type="text" className="form-control" name="cabeceraFamilia.direccion" placeholder="Dirección" defaultValue={infoAgendamiento.cabeceraFamilia.direccion} onChange={actualizarState} / >

Sorry, my English is not the best but i wanted to share my solution ... your article helped me for it

Collapse
sambeard profile image
Sam Beard • Edited

What about the usage of the state argument given in the setState callback?
As is explained in this article in the React docs you should avoid using the rendered state while updating state. Instead, the argument from the setState callback should be used like so:

this.setState(state => {
  ...state
})

However doing this with a nested state using variables that are outside of the scope is impossible:

handleEvent = (event) => {
  this.setState(state => {
    ...state,
    field: event.target.value  // event is undefined
  });
}

Any ideas how to solve this?

EDIT: meanwhile I will be using a flat state structure in order to adhere to the component update cycle. I am only using static fields for my state so far so it isn't too bad i guess.
Grouping fields in my component will now look something like this:

state = {
  field1: "",
  field2: 0,
  field3: ""
}

getFieldGroup = () => {
  let {field1, field2} = this.state;
}
Collapse
ikroeber profile image
Igor Kroeber

Dealing with nested state is not good, but we constantly need it.
I always follow the guidelines and update the whole component state instead of only updating one property. immutability is important because you make sure that your code will never break. If something changes when it shouldn't, it's easier to detect when always updating everything.

Collapse
steelvoltage profile image
Brian Barbour

Literally just ran into this today. Think I'll just make my life easier and not do nested state.

Collapse
faithfulanere profile image
Anere faithful

It’s almost impossible to escape if you are working with lots of API’s out there. There could be nested objects like say
Profile: {
First name: ‘’,
Last name: ‘’ }
Totally safer to avoid but it’s a world where everyone does things in different manner. Hi I badly wanna stay away from super nested stuff all the time but I am stuck in that loop