3

Extending from this question React Set State For Nested Object

The way to update nested state is to decompose the object and re-construct it like the following:

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

However, this is a problem for me, as it will not preserve the component class (More detail below). I am aiming for my state to be structured like the following:

this.state = {
    addressComponent: {
        street: '',
        number: '',
        country: ''
    },
    weather: {
        /* some other state*/
    }
}

To make my life easier, I created a simple address class that builds the object for me and have some other utility function such as validation.

class AddressComponent {
    constructor(street, number, country) {
        this.street = street;
        this.number = number;
        this.country = country;
    }

    validate() {
        if (this.street && this.number, this.country) {
            return true;
        }
        return false;
    }
}

This allows me to do is change the initialization of state to be:

this.state = {
    addressComponent : new AddressComponent(),
    weather: new WeatherComponent();
 }

 this will allow my view component to perform nice things like

if (this.state.addressComponent.validate()) {
    // send this state to parent container
}

The problem with this approach is that if I want to mutate one single piece of information like country and use the above Stack Overflow approach such as:

this.setState({addressComponent: {...addressComponent, country: 'bluh'}})

Doing this will mean that the resolting addressComponent is no longer part of AddressComponent class hence no validate() function

To get around it I cound recreate a new AddressComponent class every time like:

this.setState({addressComponent: new AddressComponent(this.state.street, this.state.number, 'bluh');

But this seems weird.

Am I doing this wrong? Is there a better approach? Is it acceptable to use classes like this with react?

Sasha Tsukanov
  • 1,025
  • 9
  • 20
user172902
  • 3,541
  • 9
  • 32
  • 75

3 Answers3

1

I think what you said about reinstantiating your class each time you update it in your state is the cleanest way to ensure your validate() method is called at that time. If it were me, I would probably write it as:

const { addressComponent } = this.state;
this.setState({ addressComponent: new AddressComponent(addressComponent.street, addressComponent.number, 'bluh') });
Kevin M. Lapio
  • 378
  • 1
  • 4
  • 8
1

It's undesirable to use anything but plain objects for React state to avoid situations like this one. Using class instances will also make serialization and deserialization of the state much more complicated.

The existence of AddressComponent isn't justified, it doesn't benefit from being a class.

The same code could be rewritten as functional with plain objects:

const validateAddress = address => !!(street && address.number && address.country);
...

if (validateAddress(this.state.address)) {
    // send this state to parent container
}
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • This was more of a simple example. There would be other useful case such as computing a new state from other state. So the class will handle itself instead of having the logic in the view. But if this is a bad practice (undesirable) to do I shall scrap this approach. I was trying to provide a more object orientated approach to keep my code clean rather than setting object with less control – user172902 Nov 01 '18 at 12:33
  • 1
    *There would be other useful case such as computing a new state from other state* - this can very likely be expressed with functional approach as well, feel free to update if you have problems rewriting a class to plain objects. *having the logic in the view* - maybe you need to change your view on architectural tiers because React component isn't just a 'view'. I'm OOP proponent but yes, this is considered a bad practice in React and may backfire in some places. The example with state serialization is one of those places. – Estus Flask Nov 01 '18 at 12:42
0

You can create a reusable function to update a complex state like this.

updateState = (option, value) => {
  this.setState(prevState => ({
    addressComponent: {
      ...prevState.addressComponent,
      [option]: value
    }
  })
}