3

I'm a beginner React programmer, struggling to understand how I should deal with grandchild states (or see whether it's a good idea to have grandchildren states).

My questions are...

1. Is it possible to change grandchildren via this.setState((prevState))?

2. Is having grandchildren as states a bad idea?

To illustrate, here is the constructor of a component.

constructor(props) {
 super(props);
 this.state = {
   input: {
     example: "",
   },
   invalid:{
     example: false
   },
 };
 this.handleChange = this.handleChange.bind(this);
 this.submit = this.submit.bind(this);
}

I see this.state.invalid as a child, and this.state.invalid.example as a grandchild.

Here is a JSX.

<div className="input">
   <input type="text" value={this.state.input.example || ''} onChange={this.handleChange} />
   { this.state.invalid.example ? <span> This input is invalid </span> : null }
</div>
//Here are similar input fields...

<button onClick={this.submit} type="button">Submit</button>

When the onClick={this.submit} is fired, I'd like to validate the input in the function, 'this.submit'.

submit(){
 for (var key in this.state.input) {
   if(!this.state.input[key]){
     this.setState((prevState, key) => ({
        invalid[key]:true, //This causes an error, saying that "SyntaxError: Unexpected token, expected ,"
    }));
   }
 }

In this example, this.state.invalid.example is supposed to be set (i.e. changed), but the code above doesn't work.

Maybe it'll be a better idea to decompose this app and make <div className="input"> a single component. Before doing so, I'd like to clarify whether it's possible to set grandchildren in this way.

Since I don't know anybody around me who is learning ReactJS, any advice will be appreciated!

suman j
  • 6,710
  • 11
  • 58
  • 109
Hiroki
  • 3,893
  • 13
  • 51
  • 90
  • 1
    Possible duplicate: http://stackoverflow.com/questions/18933985/this-setstate-isnt-merging-states-as-i-would-expect – forrert Feb 08 '17 at 22:11
  • Not exactly what I was looking for... but this was very informative. Thanks for sharing it! – Hiroki Feb 09 '17 at 21:47

3 Answers3

3

You can't pass state of ancestor component to descendant component by state. You can do it by props but it leads to passing props many levels down. There's also possiblity with context but it's not good way too.

The best way is to use either flux or redux. I prefer redux and in redux you can get the variables that your object needs from one store by using connect method.

https://github.com/reactjs/react-redux/blob/master/docs/api.md

check also these videos

Robert
  • 19,800
  • 5
  • 55
  • 85
  • Sorry for this late reply. I've tried many things, and now I feel like Redux is the best solution for my project. Thanks! – Hiroki May 27 '17 at 23:56
2

I don't think it is per se a bad idea to have nested state in a component. It is important to remember though, that setState

performs a shallow merge of nextState into current state

see in the react documentation.

So if your state is

{ 
  input: {
    example: "asd",
    other: "123"
  },
  invalid: {
    example: false,
    other: false
  }
}

and you call setState({invalid: {example: true}}), then the resulting state would look like

{ 
  input: {
    example: "asd",
    other: "123"
  },
  invalid: {
    example: true
  }
}

Note the everything under invalid was replaced with the new state, while the object under input was left untouched (i.e. a shallow merge was performed).

In your submit function, I would create the entire invalid object in the loop and then call setState like this: setState({invalid: invalid}).

The error in your last code segment is simply a syntax error. You can't construct an object like this: {invalid[key]:true}, it should be {invalid: {[key]: true}}.

forrert
  • 4,109
  • 1
  • 26
  • 38
  • Thank you for you code. Maybe `{invalid: {example: true}}` will work, but using this syntax with `this.setState((prevState, key))` wasn't working. Apparently `key` is treated as a field name (i.e. `invalid:{key:true}`) – Hiroki Feb 08 '17 at 22:34
  • Also, I just noticed that you call `setState` with a function as the first argument. Not quite sure where you got that from, but `setState` expects an object as the first argument, which is the merged shallowly onto the current state (i.e. a top level property will be overwritten). – forrert Feb 08 '17 at 22:40
0

If you want to do this, the way to do it is:

constructor() {
    this.state = {
        input: {
            example: "",
        },
        invalid:{
            example: false
        },
    };
}

assignChildState(state) {
    var state = Object.assign({}, this.state);
    state.input = Object.assign({}, state.input);
    state.input.example = "New Value";
    this.setState(state);
}

You could of course create a function to handle it, something like:

function assignChildState(path, value) {
    var pieces = path.split(".");
    var state = Object.assign({}, this.state);
    var current = state;
    for (var i = 0; i < pieces.length - 1; i++) {
        var piece = pieces[i];
        console.log(current, piece);
        if (current[piece].toString() === "[object Object]") {
            current[piece] = Object.assign({}, current[piece]);
        } else if (typeof current[piece] === "object" /* array */) {
            current[piece] = current[piece].concat();
        }
        current = current[piece];
    }
    current[pieces[i]] = value;
    this.setState(state);
}

And then you can do:

this.assignChildState("input.example", true);

and it will update the child state appropriately.

All that being said, I would definitely look into redux or flux, or my personal favorite flummox.

dave
  • 62,300
  • 5
  • 72
  • 93