Skip to content
This repository has been archived by the owner on Apr 13, 2021. It is now read-only.

proper way to add onChange event while keeping redux-forms/actions? #24

Closed
rileylnapier opened this issue Sep 20, 2017 · 5 comments
Closed

Comments

@rileylnapier
Copy link
Contributor

rileylnapier commented Sep 20, 2017

adding a onChange handler to my input seems to remove the redux-forms/actions

how do you recommend having both? using middleware to intercept the action? use a thunk to dispatch the redux-forms/action from my onChange action?

or maybe a new function to pass into the component like "watchValue"?

thoughts?

im basically trying to call a api whenever my input field changes. i was using onBlur... but i would like a more instant feel so im using onChange with a debounce

something like this is what i did... which i guess works? but monkeypatching

image

@oreqizer
Copy link
Owner

oreqizer commented Sep 21, 2017

You can overwrite the input.onChange and call it manually in your handler. I did a similar thing when I wanted tracking, so I created a wrapper component.

Here's a one from scratch:

import React from 'react';
import { field } from 'redux-forms-react';

class Input extends React.PureComponent {
  constructor(props) {
    super(props);

    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(ev) {
    const { input } = this.props;

    // call your custom functions here
    console.log('Side effect :: ' + ev.target.value);
    input.onChange(ev);
  }

  render() {
    const { input } = this.props;

    return (
        <input
          {...input}
          onChange={this.handleChange}
          type="text"
        />
    );
  }
}

export default field(Input);

It then produces a flow like this:
screen shot 2017-09-21 at 10 04 37

Edit

Here's an example how to have one pure field and a one with side effects, while sharing the same markup.

What I like to basically do is to create an independent component with styles and an API that I like to use without any library.

Then I create wrapper components for libraries or any modifications that I want to do:

import React from 'react';
import { field } from 'redux-forms-react';

// Input.js
// normal component with styles, not connected
const Input = props => (
  <div className="Input">
    <h3>{props.name}</h3>
    <div>Error: {String(props.error)}</div>
    <div>Dirty: {String(props.dirty)}</div>
    <div>Touched: {String(props.touched)}</div>
    <div>Visited: {String(props.visited)}</div>
    <div>Active: {String(props.active)}</div>
    <input
      id={props.name}
      name={props.name}
      onChange={props.onChange}
      onFocus={props.onFocus}
      onBlur={props.onBlur}
      type={props.type}
      placeholder={props.placeholder}
    />
  </div>
);

// InputField.js
// connected Input.js
const InputField = props => (
  <Input
    {...props.input}
    {...props.meta}
    type="text"
  />
);

// export default field(InputField);

// InputFieldTrack.js
// connected Input.js with side effects
class InputFieldTrack extends React.PureComponent {
  constructor(props) {
    super(props);

    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(ev) {
    const { input } = this.props;

    // call your custom functions here
    console.log('Side effect :: ' + ev.target.value);
    input.onChange(ev);
  }

  render() {
    const { input, meta } = this.props;

    return (
      <Input
        {...input}
        {...meta}
        onChange={this.handleChange}
        type="text"
      />
    );
  }
}

// export default field(InputFieldTrack);

Let me know if this covers your use case!

@rileylnapier
Copy link
Contributor Author

thanks! much nicer implementation :) . thanks 👍

@rileylnapier
Copy link
Contributor Author

another question. how could i listen to changes in a form?

i could create middleware and listen for "@@redux-forms/FIELD_CHANGE" and then use the getValues() selector and dispatch a new action called FORM_CHANGED?

do you think thats best? in docs you mention don't use form action names as they may have breaking changes.

thanks for all your help

@rileylnapier
Copy link
Contributor Author

or

<Form name={myForm} onChange={(formValues) => props.doThingsWithForm(formValues)} />

@oreqizer
Copy link
Owner

oreqizer commented Sep 22, 2017

I haven't thought about listening for changes of the whole form. The only kinda clean way I can think of is comparing current and previous values in componentWillReceiveProps:

// ...code
componentWillReceiveProps(nextProps) {
	if (this.props.values !== nextProps.values) {
		// form changed, do stuff
	}
}
// ...code

export default connect(state => ({
  values: getValues('yourform', state),
}))(YourForm);

I will consider introducing an API for this use case if this solution turns out to be non-practical.

With that said, I try to keep any change listeners per-field, thus making something like you provided the first example of - I supply an optional onChangeField (or whatever you name it) callback that is called whenever the field changes. When it is called, I'd handle it in your form where you have an access to getValues('yourform', state),.

It makes you pass the callback to all your fields, which may seem not very DRY, but is explicit and guaranteed to work. Try both approaches and let me know!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants