0

Currently i am playing with some user editor in react. Of course i came across reacts principle: 'Never mutate this.state directly'

Suppose the following snippet for a simple user editor (what isn't shown: the user object will be pushed to a web service as json on buttonSave click):

import React, { Component } from 'react';

class App extends Component {
    constructor(props) {
        super(props);
        this.onChange = this.onChange.bind(this);
        this.state = {
            user: {
                name: 'me',
                parents: {
                    mother: 'mary',
                    father: 'john',
                },
                hobbies: [
                    {
                        name: 'soccer',
                        location: 'home'
                    },
                    {
                        name: 'tennis',
                        location: 'home'
                    }
                ]
            }
        };
    }

    onChange(e) {
        let user = this.state.user; // 1 - no copy - reference
        //let user = {...this.state.user}; // 2 - shallow copy
        //let user = JSON.parse(JSON.stringify(this.state.user)); // 3 - deep copy

        switch (e.target.dataset.ptype) {
            case 'me':
                user.name = e.target.value;
                break;
            case 'mother':
                user.parents.mother = e.target.value;
                break;
            case 'father':
                user.parents.father = e.target.value;
                break;
            case 'hobby':
                user.hobbies[1].name = e.target.value;
                break;
            default:
                break;
        }

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

    render() {
        return (
            <div>
                <div><input data-ptype='me' onChange={this.onChange} value={this.state.user.name}/>{this.state.user.name}</div>
                <div><input data-ptype='mother' onChange={this.onChange} value={this.state.user.parents.mother}/>{this.state.user.parents.mother}</div>
                <div><input data-ptype='father' onChange={this.onChange} value={this.state.user.parents.father}/>{this.state.user.parents.father}</div>
                <div><input data-ptype='hobby' onChange={this.onChange} value={this.state.user.hobbies[1].name}/>{this.state.user.hobbies[1].name}</div>
                <div><pre>{JSON.stringify(this.state.user, null, 2)}</pre></div>
            </div>
        )
    }
}

export default App;

In the onChange method i tried three different approaches for updating the current user object:

  1. Reference
  2. Spread Operator
  3. JSON.stringify

Every approach works as expected.

What are the drawbacks in this scenario of the different approaches ?

Sure, if i only update the user object via reference a empty call of setState will reflect the changes too.

Is there any overview available how setState handles/evaluates an updated state object for rendering ?

br, Susi

SusiFu
  • 3
  • 3

1 Answers1

0

There's not really an ideal way to handle nested state, either you have a generic function to handle all updates to the object, or you write lots of individual functions to handle how each value should be updated. Each has their pros and cons.

Your question asks how best to clone an object. That's really a separate question (answered here) but I'd suggest that you use the object spread syntax as below:

const user = {
  name: 'me',
  parents: {
    mother: 'mary',
    father: 'john',
  },
  hobbies: [{
      name: 'soccer',
      location: 'home'
    },
    {
      name: 'tennis',
      location: 'home'
    }
  ]
};

const addUserHobby = (user, newHobby) => ({
  ...user,
  hobbies: [
    ...user.hobbies,
    newHobby 
  ]
})

const updatedUser = addUserHobby(user, {
  name: 'hockey',
  location: 'home'
})

console.dir(updatedUser)
OliverRadini
  • 6,238
  • 1
  • 21
  • 46