How to update nested state properties in React

后端 未结 26 1839
野趣味
野趣味 2020-11-21 06:35

I\'m trying to organize my state by using nested property like this:

this.state = {
   someProperty: {
      flag:true
   }
}

But updating

相关标签:
26条回答
  • 2020-11-21 07:12
    stateUpdate = () => {
        let obj = this.state;
        if(this.props.v12_data.values.email) {
          obj.obj_v12.Customer.EmailAddress = this.props.v12_data.values.email
        }
        this.setState(obj)
    }
    
    0 讨论(0)
  • 2020-11-21 07:13

    To make things generic, I worked on @ShubhamKhatri's and @Qwerty's answers.

    state object

    this.state = {
      name: '',
      grandParent: {
        parent1: {
          child: ''
        },
        parent2: {
          child: ''
        }
      }
    };
    

    input controls

    <input
      value={this.state.name}
      onChange={this.updateState}
      type="text"
      name="name"
    />
    <input
      value={this.state.grandParent.parent1.child}
      onChange={this.updateState}
      type="text"
      name="grandParent.parent1.child"
    />
    <input
      value={this.state.grandParent.parent2.child}
      onChange={this.updateState}
      type="text"
      name="grandParent.parent2.child"
    />
    

    updateState method

    setState as @ShubhamKhatri's answer

    updateState(event) {
      const path = event.target.name.split('.');
      const depth = path.length;
      const oldstate = this.state;
      const newstate = { ...oldstate };
      let newStateLevel = newstate;
      let oldStateLevel = oldstate;
    
      for (let i = 0; i < depth; i += 1) {
        if (i === depth - 1) {
          newStateLevel[path[i]] = event.target.value;
        } else {
          newStateLevel[path[i]] = { ...oldStateLevel[path[i]] };
          oldStateLevel = oldStateLevel[path[i]];
          newStateLevel = newStateLevel[path[i]];
        }
      }
      this.setState(newstate);
    }
    

    setState as @Qwerty's answer

    updateState(event) {
      const path = event.target.name.split('.');
      const depth = path.length;
      const state = { ...this.state };
      let ref = state;
      for (let i = 0; i < depth; i += 1) {
        if (i === depth - 1) {
          ref[path[i]] = event.target.value;
        } else {
          ref = ref[path[i]];
        }
      }
      this.setState(state);
    }
    

    Note: These above methods won't work for arrays

    0 讨论(0)
  • 2020-11-21 07:14

    Although you asked about a state of class-based React component, the same problem exists with useState hook. Even worse: useState hook does not accept partial updates. So this question became very relevant when useState hook was introduced.

    I have decided to post the following answer to make sure the question covers more modern scenarios where the useState hook is used:

    If you have got:

    const [state, setState] = useState({ someProperty: { flag: true, otherNestedProp: 1 }, otherProp: 2 })
    

    you can set the nested property by cloning the current and patching the required segments of the data, for example:

    setState(current => { ...current, someProperty: { ...current.someProperty, flag: false } });
    

    Or you can use Immer library to simplify the cloning and patching of the object.

    Or you can use Hookstate library (disclaimer: I am an author) to simply the management of complex (local and global) state data entirely and improve the performance (read: not to worry about rendering optimization):

    import { useStateLink } from '@hookstate/core' 
    const state = useStateLink({ someProperty: { flag: true, otherNestedProp: 1 }, otherProp: 2 })
    

    get the field to render:

    state.nested.someProperty.nested.flag.get()
    // or 
    state.get().someProperty.flag
    

    set the nested field:

    state.nested.someProperty.nested.flag.set(false)
    

    Here is the Hookstate example, where the state is deeply / recursively nested in tree-like data structure.

    0 讨论(0)
  • 2020-11-21 07:15

    Although nesting isn't really how you should treat a component state, sometimes for something easy for single tier nesting.

    For a state like this

    state = {
     contact: {
      phone: '888-888-8888',
      email: 'test@test.com'
     }
     address: {
      street:''
     },
     occupation: {
     }
    }
    

    A re-useable method ive used would look like this.

    handleChange = (obj) => e => {
      let x = this.state[obj];
      x[e.target.name] = e.target.value;
      this.setState({ [obj]: x });
    };
    

    then just passing in the obj name for each nesting you want to address...

    <TextField
     name="street"
     onChange={handleChange('address')}
     />
    
    0 讨论(0)
  • 2020-11-21 07:18

    If you are using ES2015 you have access to the Object.assign. You can use it as follows to update a nested object.

    this.setState({
      someProperty: Object.assign({}, this.state.someProperty, {flag: false})
    });
    

    You merge the updated properties with the existing and use the returned object to update the state.

    Edit: Added an empty object as target to the assign function to make sure the state isn't mutated directly as carkod pointed out.

    0 讨论(0)
  • 2020-11-21 07:18

    This is my initialState

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

    The hook or you can replace it with the state (class component)

    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} />
    
    0 讨论(0)
提交回复
热议问题