0

I got a obj in state and all by default are false:

obj: {
  field1: false,
  field2: false,
  field3: false
}

I want to set all to true after submit, all are dynamic:

handleSubmit = (e) => {
  e.preventDefault();

  const doValid = {
    field1: true,
    field2: true,
    field3: true,
  }

//this is static, in real code it comes from validator and decide to be false or true

  for (let i = 0; i < Object.keys(doValid).length; i++) {
    this.handleValid(Object.keys(doValid)[i], Object.values(doValid)[i]);
  }
}

And here is I set each state to true dynamically:

handleValid = (type, v) => {

  this.setState({
    ...this.state,
    obj: { ...this.state.obj,
      [type]: [v]
    }
      })

}

As you see, I used [type] and [v] for this, but the problems:

  1. When I click on submit button, state not change to true (I know maybe it's a delay to get changed states) (if I right ignore this one)
  2. If I click again, now I see just last item changed to true, but here is two problems:

a: Why just last item changed? b: Why it changed like this? with bracket, that make it to array but it just a dynamic to decide it's true or false:

field1: false
field2: false
field3: [true]

It should be like this:

field1: true
field2: true
field3: true

JSFiddle

(See console log please in jsfiddle)

0

4 Answers 4

2

You just need to do like:

this.setState({
  ...this.state,
  obj: {
   ...this.state.obj,
   [type]:v
  }
})

BTW, I would be doing simply like instead of for loop:

this.setState({
  ...this.state,
  obj: {
   ...this.state.obj,
   ...doValid
  }
})

FYI, Inside the loop, this.state is returning the previous value, as the pending state update has not been applied yet. See this post for more information.

Here's a working fiddle for you:

https://jsfiddle.net/09x2g5L6/

enter image description here

Sign up to request clarification or add additional context in comments.

7 Comments

Nothing changed JSFiddle still are false not true
Still just last one changed
I tested your update, but error: doValid is not defined
@BhojendraRauniyar Don't bother, I already added one in my answer. OP doesn't know scope so gave him a reference to the ydkjs books on scope.
@BhojendraRauniyar and @MHR both answer working here, but not understand something, I need to pass doValid to handleValid to get respond from my validator, you both removed handleValid , now I don't know how can I do this with your codes
|
1

When determinig new values of state based on the previous values, we pass a function to setState to refer to previous state values.

setState() also accepts a function. This function accepts the previous state and current props of the component which it uses to calculate and return the next state.

  handleValid = (type, v) => {
    this.setState(prevState => ({
      ...prevState,
      obj: { ...prevState.obj, [type]: v },
    }));
  };

Why pass a function to setState? The thing is, state updates may be asynchronous.

Think about what happens when setState() is called. React will first merge the object you passed to setState() into the current state. Then it will start that reconciliation thing. It will create a new React Element tree (an object representation of your UI), diff the new tree against the old tree, figure out what has changed based on the object you passed to setState() , then finally update the DOM

Please follow this beautiful explanation of functional setState and what happens when we call setState() link

1 Comment

I want to go for simpler code but the matter is they remove handleValid, I'm little confused how can I include my validator in that codes. your code seems to working fine, let me check on my project and pick the best answer.
1

It looks like you wrote a lot of complicated code just to do:

this.setState({
  ...this.state,
  obj: { ...this.state.obj, ...doValid },
});

You seem to have trouble understanding scope, here is a good book on that and I'd advice you read the whole series as it's a great series.

Here is a working example of my code:

class App extends React.Component {
  state = {
    obj: {
      field1: 2,
      field2: 3,
      field3: 4,
    },
  };
  clickHandler = () => {
    const doValid = {
      field1: true,
      field2: true,
      field3: true,
    };
    this.setState({
      ...this.state,
      obj: { ...this.state.obj, ...doValid },
    });
  };
  render() {
    return (
      <div>
        <button onClick={this.clickHandler}>
          click me
        </button>
        <pre>
          {JSON.stringify(this.state, undefined, 2)}
        </pre>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

3 Comments

Please test it on jsfiddle, it has error and not working
@tourtravel Ah, the "not working", very helpful indeed. Do you get errors or what is it that "is not working"? Is it the code or maybe the root cause can be found somewhere else?
You can see the errors in jsfiddle console log.. anyway: doValid is not defined
0

You need to change [v] to v. When you use brackets, it thinks it as an array and makes it an array.

You are trying to change state simultaneously and when you are changing it, you are copying the old state, because it didn't change yet. setState doesn't happen simulaneously.

You need to remove handleValid function and change handleSubmit with below code.

handleSubmit = (e) => {
            e.preventDefault();
            console.log(this.state.obj);

                    const doValid = {
                        field1: true,
                        field2: true,
                        field3: true,
                    }
        //you need to copy the state and change its values. You setState after you are done with it.

      let newState = {...this.state};
      for (let i = 0; i < Object.keys(doValid).length; i++) {
          newState.obj[Object.keys(doValid)[i]] = Object.values(doValid)[i];
      }
      //now we made our new state and will update it.
      this.setState(newState, () => {
       //after change successfully happened, this code will fire.
       console.log(this.state.obj);
      })
    }

3 Comments

Terrible answer, you make a shallow copy of state and then mutate state. Try to run the following in the console: const person = {address:{street:'org'}}; const copy = {...person}; copy.address.street='changed'; console.log(person.address.street);
@HMR I thought he wants to update state while looping arrays. That is why I wrote this, if it was just copying object, your code makes sense too.
The point is that newState is a shallow copy so only copy.level1=newValue will not mutate the original but copy.level1.level2 will mutate. Also doing the update in a for loop is much more complicated then just spreading doValid into the state. My first comment may be a bit crude but stand with the opinion that this answer as it stands isn't very useful.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.