I (and I am pretty sure you as well) sometimes get the feeling that there is too much boilerplate when it comes to implementing forms with React and Redux. Connecting actions, validations, state updates and rendering for a simple form can be a tedious job. I know there are people working on good wrappers but I am not sure we are there yet.

I hope that over time we will get better abstractions that will remove this boilerplate for simplest situations. And we will only need to focus on more complex or unusual interactions which differ from the default of “update of this input means update of that property in the state”.

In the meantime I wanted to present (perhaps obvious) techniques that can reduce the amount of code.

Are you or your company looking for React.js developers? - your job listing can be here and reach thousands of programmers within a month.

Imagine that you have an application for managing season pass holders on a stadium. We can see who is sitting where during the matches, see which seats are sold when, get a list of normal buyers as well as season pass holders and many other things. Once we select a seat we can add a season pass holder manually. We need to select a few attributes and provide their personal data such as:

  • email
  • name
  • phone
  • city
  • zip code
  • street
  • country

I would say that the default solution would be that a change of any of those attributes would trigger a separate kind of action such as CHANGE_EMAIL or CHANGE_NAME. And sometimes such granularity is good and valuable because different reducers, different parts of your application might need to react differently to those actions. Sometimes it might be much easier for reducers if they can distinguish between those two particular use-cases.

But sometimes, on the other hand, this is just boilerplate. And not a single part of your app cares whether all those attributes were changed together or not. In such case, you might want to simplify your solution by dispatching just one action with all the attributes.

class AddMembership extends Component {
  constructor(props) {
    super(props);
    this.onInternalChange = this.onInternalChange.bind(this);
  }

  onInternalChange(propName, newValue){
    let newProps = {...this.props, [propName]: newValue};
    this.props.onChange(newProps);
  }

  render() {
    const {
      name,
      email,
      phoneNumber,
      city,
      countryCode,
      zipCode,
      street,
    } = this.props;

    return (
      <form>
        <label>
          Name
          <input
            required
            value={firstName}
            onChange={(e) => this.onInternalChange("name", e.target.value)}
          />
        </label>
        // ...

I am aware that creating new function on every render is an anti-pattern but this component does not need top performance ;)

And your reducer can handle this quite easily as well.

function membership(state = defaultMembershipState, action) {
  switch(action.type) {
    # ...
    case CHANGE_MEMBERSHIP:
      let {
        name,
        email,
        phoneNumber,
        city,
        countryCode,
        zipCode,
        street,
      } = action;
      return {
        ...state,
        name,
        email,
        phoneNumber,
        city,
        countryCode,
        zipCode,
        street,
      };

If in the future it turns out that changing email is somehow more important and for the simplicity of the solution it would be better to have a separate action only for that, you can easily change it later. Dispatch CHANGE_EMAIL in such case, and CHANGE_MEMBERSHIP in other (less important) cases.

I think it is ok, to lose some of the intention (what was exactly changed now, an email, a name or a phone) if nothing in your system cares right now. You can always make it more specific later.

An alternative approach

Instead of publishing an action with all fields, you can still publish and handle a small general action, but with a field name and value.

function membership(state = defaultMembershipState, action) {
  switch(action.type) {
    # ...
    case CHANGE_FIELD:
      return {
        ...state,
        [action.fieldName]: action.fieldValue
      };

Conculsion

It’s very easy to go full-warp with very specific events, dedicated for every change that occurs when a user is editing forms such as CHANGE_NAME, CHANGE_STREET, CHANGE_AGE, etc…

But it is not actually necessary if the logic behind the update is not different for all those fields. For those parts which require different handling in various reducers; dedicated action is the best solution. For others, sometimes a general action might be good enough.

Did you like it?

Subscribe now to our newsletter to continue receiving free React.js lessons.

Disclaimer

  • This is an oversimplified example to show you the idea :)
comments powered by Disqus