Search code examples
reactjsreduxredux-form

Overriding onChange behavior with React / Redux Forms


I'm building a complex form in React and am trying to use Redux Forms as a helper tool.

The requirements of the project are that:

  1. There will be values on the page that change depending on the input fields (like an excel spreadsheet)

  2. There will be at least one field that functions both as an input field and a dependent field (it can be changed, but also changes when other fields are changed)

  3. I want to control when the state changes. By default, it will happen instantly when inputs change, but I'd rather have a delay like this example.

The first function basically follows the tutorial of Redux Forms, I have that working. However, the other two require modifying what happens when a field updates and I am having trouble figuring out how to do that. I could imagine how to do this from scratch, but wanted to see if there was a way to use this library to keep things simpler.

My code is up on GitHub (side question, does anyone have insight why my GitHub pages site gives a 404?


Solution

  • You would have to create a custom component to place inside of redux-form's Field, in which you hold an internal state, and sync this with the formReducer whenever you want.

    You can achieve this in a couple of steps:

    1. Create a custom component to use inside of Field. This component is injected with a meta and input prop.

    2. Create your state for your React Component in which you will keep track of the data that you'll eventually emit to the formReducer.

    3. In your constructor, use props.input.value to set your initial state. If you do this, you can use the 'initialValues' object for your reduxForm.

    4. Use connect by react-redux to make it possible to use react-form's action creators. In your case you'll be using the change action creator.

    5. Create your render function with your input-field and fire the change action to modify your formReducer's value for this field.

    So that comes down to something like this:

    <Field
      name="daysPerWeek"
      component={MyCustomComponent} // 1
    />
    
    ...
    
    class MyCustomComponent {
      constructor(props) {
        super(props);
    
        this.state = {
          value: props.input.value, // 2 and 3
        }
      }
    
      ....
    }
    

    4:

    import { connect } from 'react-redux';
    import { change } from 'react-form';
    
    const mapDispatchToProps = (dispatch, ownProps) => ({
      ourCustomChange: (value) => dispatch(change(ownProps.meta.form, ownProps.input.name, value))
    })
    
    export default connect(undefined, mapDispatchToProps)(MyCustomComponent);
    

    5:

    ....
     componentDidUpdate(prevProps, prevState) {
       if (prevState.value !== this.state.value) {
         this.debounceAndEmit();
       }
     }
    
     debounceAndEmit() {
       // Debounce for some time. Maybe use:
       // import { debounce } from 'throttle-debounce';
       // for that:
       debounce(2000, () => {
         this.props.ourCustomChange(this.state.value)
       })
     }
    
     handleChange(event) {
       // Do things here like trimming the string, regex, whatever.
       this.setState({ value: event.target.value })
     }
    
      render() {
        return (
          <input
            {...this.props.input} // Includes standard redux-form bindings for input fields. 
            value={this.state.value}
            onChange={this.handleChange.bind(this)}
          />
        )
      }
    ....
    

    In some cases you might have to use the blur action creator too. For when you're doing stuff when clicking outside of an input field for example.

    If you want form fields to change depending on other fields, you should use selectors to inject their values into your custom component to respond to that.

    Does this answer your question?