user2029958 user2029958 - 4 months ago 127
Javascript Question

How to properly manage forms in React/Redux app

I am following some tutorials about React/Redux.
The part that is not clear to me is input forms.
I mean how to handle changes and where to manage state
This is a very simple form with just one field.

export default class UserAdd extends React.Component {

static propTypes = {
onUserSubmit: React.PropTypes.func.isRequired
}

constructor (props, context) {
super(props, context);
this.state = {
name: this.props.name
};
}

render () {
return (
<form
onSubmit={e => {
e.preventDefault()
this.handleSubmit()
}}
>
<input
placeholder="Name"
onChange={this.handleChange.bind(this)}
/>
<input type="submit" value="Add" />
</form>
);
}

handleChange (e) {
this.setState({ name: e.target.value });
}

handleSubmit () {
this.props.onUserSubmit(this.state.name);
this.setState({ name: '' });
}
}


I feel this like breaking Redux philosophy, because a presentation component is updating the state, am I right?
This is the connected component to be coupled with the presentation component.

const mapDispatchToProps = (dispatch) => {
return {
onUserSubmit: (name) => {
dispatch(addUser(name))
}
}
}

const UserAddContainer = connect(
undefined,
mapDispatchToProps
)(UserAdd)


Is this the correct way to follow, or am i mixing things up?
Is correct to call setState in UserAdd component and updating state on every key pressed (handleChange) ?

Thank you

Answer

There is a nice library Redux Form for handling forms by updating global store in a Redux way. With it's help you shouldn't have to set up actions for each input, just the whole form and its state. Check it out.

The main principle of this library, consists in updating inputs value by dispatching redux actions, not using setState stuff. For every form in the app, there is a separate property in the global state. Every blur, onChange, submit events dispatches an action that mutates the state. Action creators are common for all the forms, no need to declare them for every form apart, just pass form id or name in payload to the reducer, so it could know which form`s property should be updated.

For example. There should be set a property form as a plain object in the app state. Each new form in the application, should store it's state in it. Let's give your form a name attribute, so it should serve us as the identificator.

render () {
return (
    <form
        name="AForm"
        onSubmit={e => {
            e.preventDefault()
            this.handleSubmit()
        }}
    >
        <input
            name="name"
            placeholder="Name"
            onChange={this.handleChange.bind(this)}
        />
        <input type="submit" value="Add" />
    </form>
);

}

Since it has just one property Name, form state should now have a structure like:

form: {
  AForm: {
     Name: {
       value: '',
       error: ''
     }
  }
} 

Also, there should be an action creator:

export function onFormFieldChange(field) {  
  return {
    type: "redux-form/CHANGE"
    field: field.name
    value: field.value,
    form: field.form
  }
}

All needed data should be passed as the pay load so, the reducer will know now what form and what field to update.

Now, when the form component is being connected, this action creator should be set as a property:

import { onFormFieldChange } from `path-to-file-wit-actions` 
const mapStateToProps = (state, ownProps) => {
  const { AForm } = state.form
  return {
   name: AForm.name
  }
} 
const mapDispatchToProps = (dispatch) => {
  return {
    onChange: (e) => {
      dispatch(onFormFieldChange({
        field: 'name',
        value: e.target.value,
        form: 'AForm'
      }))
    },
    onUserSubmit: (name) => {
      dispatch(addUser(name))
    }
  }
}

const UserAddContainer = connect(
  undefined,
  mapDispatchToProps
)(UserAdd)

In the component, field value and onChange event handler should now be taken from props:

<input placeholder="Name" name="this.props.name.value"  onChange={this.props.handleChange.bind(this)} />

So, form is being handled in a "Redux" way. On every key press, global state will be updated and input will be rerendered with it's new value. Similar thing should be done with other events, like onBLur, onFocus, onSubmit etc. Since it's a lot work to do, it's much more comfrotable to use Redux Form.

It's a very rough example. Nearly each line of code could be enhanced, hope you'll understand what was meant.