user2465134
user2465134

Reputation: 9753

React - Have One Main setState Function or Multiple?

I've been developing in React for a while. When I first started, my code would look like this:

this.state = {
  foo: 1,
  bar: 2,
  baz: 3
};

updateFoo(num) {
  this.setState({ foo: num )};
}

updateBar(num) {
  this.setState({ bar: num )};
}

updateBaz(num) {
  this.setState({ baz: num )};
}

A separate function to update a separate part of state.

Later on I realized, hmm that is pretty dedundant, let's change things up. So instead of a separate function for each state update, I went to something like this:

updateState(key, val) {
  this.setState([key]: val);
}

I've been using updateState for very simple state updates for a little while now. I like it but I realized that I can go a step further and pass an entire update object for updating multiple states:

updateManyStates(state) {
  this.setState(state);
}

updateManyStates({ foo: 5, bar: 6, baz: 7 });

After writing the updateManyStates function I felt my code was becoming less clear. Though redundant, the functions like updateFoo, updateBar, updateBaz gave a clear picture of what was going on in my code. updateManyStates is very DRY but at times I feel like it's more clever than digestible for another developer. Thoughts?

Upvotes: 2

Views: 1530

Answers (2)

DanneManne
DanneManne

Reputation: 21180

I would say that the thing to keep in mind is to try and be as consistent as possible with whatever pattern and naming standard you choose. If you think having multiple update methods creates clear, readable code, then stick with it.

Personally, I think that not only the function pattern, but also the naming standard is important to create readable code. For example, I rarely create functions with update... in their name, inside a Component. That naming standard is more common as an Action handler for me. What I try to do inside my components is to have methods named handle..., i.e. handleChange, handleClick, etc. So the call to setState is mostly called from within those handle function. And if those methods are passed down to child components, they would be assigned to on... properties, i.e. onChange, onClick.

But to relate to your original question, since I have multiple functions as handlers, the call to setState is repeated in multiple functions and contexts.

As a side-note, I do see a problem in your example, and that is that each call to setState will replace all existing properties. For example, if you start out with the initial state

this.state = {
  foo: 1,
  bar: 2,
  baz: 3
};

And then you call this.setState({ foo: num )};, then this.state does no longer have any bar or baz keys/values, since the entire object has been replaced. What the ReactJS documentation suggests for handling this, is to use their immutable helper function update (see Immutability Helpers - React). Depending on which React version you use, you can find it in different packages.

To only update one key/value and let the rest remain, here is what you do:

this.setState(update(this.state, {
  foo: { $set: "new value" }
}));

Here is an example of what could be a basic component from my projects.

import { updateUser } from '../actions/UserActions';

class MyComponent extends Component {

  constructor(props) {
    super();

    this.state = {
      first: "",
      last: "",
      hasChanges: false
    };

    this.handleChangeFirst = this.handleChangeFirst.bind(this);
    this.handleChangeLast = this.handleChangeLast.bind(this);
    this.handleClickSave = this.handleClickSave.bind(this);
  }

  handleChangeFirst(event) {
    this.setState({
      first:      event.target.value,
      hasChanges: true
    });
  }

  handleChangeLast(event) {
    this.setState({
      last:       event.target.value,
      hasChanges: true
    });
  }

  handleClickSave() {
    const { first, last } = this.state;

    updateUser({ first, last });

    this.setState({
      hasChanges: false
    });
  }

  render() {
    const { first, last, hasChanges } = this.state;

    return (
      <div>
        <input
          type="text"
          value={first}
          onChange={this.handleChangeFirst}
        />
        <input
          type="text"
          value={last}
          onChange={this.handleChangeLast}
        />
        <input
          type="button"
          value="Save"
          onClick={this.handleClickSave}
          disabled={!hasChanges}
      </div>
    );
  }
}

And as a last tip if you are concerned about DRY-ing your code, I could achieve the same result as the above example, by binding the handle function multiple times to different attributes, like this:

constructor(props) {
  super();

  this.state = {
    first: "",
    last: ""
  };

  this.handleChangeFirst = this.handleChange.bind(this, "first");
  this.handleChangeLast = this.handleChange.bind(this, "last");
}

handleChange(attr, event) {
  this.setState(update(this.state, {
    [attr]:     event.target.value,
    hasChanges: true
  });
}

Maybe I overcomplicated things, but I hope this can give some ideas on how you want to structure things, going forward.

EDIT: Gave wrong information about setState.

Upvotes: 1

Danny Delott
Danny Delott

Reputation: 6998

There is nothing wrong with having nicely named methods that perform specific updates to local component state. I prefer it, actually, as these methods often get passed around to other components downstream and makes tracing the logic easier.

Calling this.setState in each method is hardly at odds with the notion of DRY.

Upvotes: 1

Related Questions