Reputation: 9753
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
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
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