New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v6 form onChange #883

Closed
aight8 opened this Issue Apr 29, 2016 · 29 comments

Comments

@aight8
Copy link

aight8 commented Apr 29, 2016

I would like to listen if the form changes some value. Then submit myself the form at every change (there are not text inputs but checkboxes etc.) I look at the easiest solution.. but I figured out that I have to listen at Field component level on changes, wrap the field props onChange and execute the handleSubmit method after it executes the fields onChange method one.

Maybe we can introduce a onChange method in the reduxForm configuration?

So #806 this issue could be solved too with this

@erikras erikras added the enhancement label Apr 29, 2016

@erikras erikras added this to the next-6.0.0 milestone Apr 29, 2016

@erikras

This comment has been minimized.

Copy link
Owner

erikras commented Apr 29, 2016

So the form does a deep-equal check in componentWillReceiveProps and if the values have changed and it has an onChange prop, it calls this.props.onChange(values)?

Seems doable.

@aight8

This comment has been minimized.

Copy link

aight8 commented Apr 29, 2016

yap when it not eats to much performance. Don't know exactly the inner architecture, but already analyzing it :)

Currently this is my workaround:

<Radio
   {...field}
   onChange={value => {
      field.onChange(value);
      handleSubmit();
   }}
   options={options}
 />

To every field.

@aight8

This comment has been minimized.

Copy link

aight8 commented Apr 29, 2016

Oh no, it not works this way neither. When handleSubmit executes the onSubmit in the reduxForm it receives the old values before field.onChange(value);is called.

An idea how to solve that? 🎃

@erikras

This comment has been minimized.

Copy link
Owner

erikras commented May 1, 2016

This is a common mistake. I really should make a FAQ about this. It looks reasonable and declarative, but handleSubmit submits the values as they currently are in the props, and the props don't get updated from Redux until the next process tick. You have two options; pick that which least disgusts you.

<input {...field} onChange={event => {
  field.onChange(event)
  setTimeout(handleSubmit)
}}/>

or

<input {...field} onChange={event => {
  field.onChange(event)
  handleSubmit(values => {
    this.props.onSubmit({
      ...values,
      myField: event.target.value // <--- manually insert this field's value into submitted json
    })()
  })
}}/>
@paulshen

This comment has been minimized.

Copy link

paulshen commented May 4, 2016

Thanks for the reply! In case you're following along (like I was), the last snippet should be

handleSubmit(values => {
  this.props.onSubmit({
    ...values,
    myField: event.target.value
  });
})(); // add parentheses here
@erikras

This comment has been minimized.

Copy link
Owner

erikras commented May 4, 2016

Thanks. Updated my comment.

@nktssh

This comment has been minimized.

Copy link
Contributor

nktssh commented Jun 6, 2016

@erikras thanks for sharing this. You should definitely add this in FAQ!
But what if I want to fire callback only if the value is valid (e.g. no error passed to component property via validate function)? How I can handle this situation?
Manually calling the validate function seems a little bit overhead...

@calvinfroedge

This comment has been minimized.

Copy link

calvinfroedge commented Aug 5, 2016

I think it would definitely be useful to have a change handler at the form level.

@enkay

This comment has been minimized.

Copy link

enkay commented Aug 11, 2016

Option 1 doesn't seem to work, and with option 2 is there a reason the input field would lose focus after submit?

@miracle2k

This comment has been minimized.

Copy link

miracle2k commented Aug 22, 2016

Man, React form handling sure sucks.

What lead me to this ticket is that my submit button is outside of my form component. I want to access properties like "dirty" or "anyTouched" to decide whether to show the save button at all, or whether to disable it.

Things I've considered (both of them feel unclean):

  1. Move the reduxForm decorator to the parent container, using propNamespace to easily pass down the form props to the actual form component. This is my preferred solution, but decoupling the form and the @reduxForm config seems wrong.
  2. @connect manually to the store to get the data I want; but that means I cannot access instance-calculated state like "dirty" - it's not in the store.
@yourfavorite

This comment has been minimized.

Copy link

yourfavorite commented Aug 27, 2016

@erikras Any tips on how to handle the onChange treatment in your comment above now that we're in v6.0.1 where input is out and Field is in? What I have below obviously doesn't work. I'm unsure how to reference the exiting redux-form onChange event.

            <Field
              name="freq"
              component="input"
              type="number"
              onChange={event => {
                Field.onChange(event);
                updateSite();
              }}
            />
@oyeanuj

This comment has been minimized.

Copy link
Contributor

oyeanuj commented Sep 1, 2016

@erikras Did this make it to 6.0 release?

@erikras

This comment has been minimized.

Copy link
Owner

erikras commented Sep 2, 2016

Did this make it to 6.0 release?

It did not, I'm afraid. But it's still got the Enhancement tag, and I filter by that when deciding what to implement next.

@pezza3434

This comment has been minimized.

Copy link

pezza3434 commented Sep 6, 2016

For @yourfavorite and for anyone else having this issue, you need to do something like this:


const renderField = (field) => (
    <input {...field.input} type="text" onChange={(e) => {
        field.onChangeAction(field.input.value);
        field.input.onChange(e);
    }}/>
);

const SearchFormComponent = React.createClass({
    render() {
        return (
            <div>
                <span className="search__input">
                    <Field name="search" onChangeAction={this.props.onChangeAction} component={renderField} ref="search" />
                </span>
            </div>
        )
    }
});
@srosset81

This comment has been minimized.

Copy link

srosset81 commented Sep 6, 2016

Thanks @pezza3434! But instead of field.input.value, you need to use e.target.value, otherwise you won't get the actual value of the field, but the previous one.

Here's the solution I use:

export class Input extends Component {

    updateValue(e) {
        if( this.props.onChangeAction!==undefined )
                this.props.onChangeAction(e.target.value);

        this.props.input.onChange(e);
    }

    render() {
        const {input, label, type, meta: {asyncValidating, touched, error}} = this.props;

        return (

            <div className={`form-group ${touched && error ? 'has-error' : ''}`}>
                <label className="control-label">{label}</label>
                <div className={asyncValidating ? 'async-validating' : ''}>
                    <input type={type} className="form-control" {...input} onChange={this.updateValue.bind(this)}/>
                </div>
                <div className="help-block">
                    {touched ? error : ''}
                </div>
            </div>
        );
    }
};
@Billy-

This comment has been minimized.

Copy link

Billy- commented Nov 22, 2016

@pezza3434 I have used your implementation and it works - except the first input on an empty field causes the field to be de-focused. Does anyone have any idea why this might be? Here's my code:

import React from 'react'
import { connect } from 'react-redux'
import { Field, reduxForm, formValueSelector } from 'redux-form'
import { submitSettings } from '../actions/settingsActions'

let Settings = (state) => {
	const renderField = (field) => (
		<input {...field.input} onChange={(e) => {
			field.onChangeAction(field.input.value)
			field.input.onChange(e)
		}}/>
	)
	console.log(state)
	let { handleSubmit, onSubmit, onChange, settingsData } = state
	let submit = handleSubmit(onSubmit)
	return <form onSubmit={submit}>
		<h2>Settings</h2>
		{ state.isLoading && <p>Loading...</p> }
		{ state.errorMsg && <p><b>{state.errorMsg}</b></p> }
		<label>Repository (user/repo)
			<Field name="repo" component={renderField} type="text" onChangeAction={_ => onChange(submit)}/>
		</label>
		<label>Hook secret
			<Field name="secret" component={renderField} type="text" onChangeAction={_ => onChange(submit)}/>
		</label>
	</form>
}
Settings = reduxForm({form: 'settings'})(Settings)

const mapStateToProps = (state, ownProps) => {
	return { ...state.settings.form }
}

let t
const mapDispatchToProps = (dispatch, ownProps) => {
	return {
		onSubmit: (formData) => {
			console.log('Form submit')
			//dispatch(submitSettings(formData))
		},
		onChange: (submit) => {
			console.log('onChange')
			clearTimeout(t)
			t = setTimeout(submit, 1000)
		}
	}
}
Settings = connect(mapStateToProps, mapDispatchToProps)(Settings)

export default Settings

Here's the redux logger output:
image

@Billy-

This comment has been minimized.

Copy link

Billy- commented Nov 22, 2016

Please see ~~~http://dl-sc-issues.uel.ac.uk/~~~* for live replication of issue - create a user and you will be taken to the "settings" page, which exhibits this bug. Just try entering multiple characters into either field.

EDIT: Having just tried that link outside the VPN, I've realised it does not work. Attempting to launch something in the cloud now...

@Billy-

This comment has been minimized.

Copy link

Billy- commented Nov 26, 2016

Ok I have re-deployed in a droplet: http://178.62.30.69/ Redux dev tools is also enabled.

@Billy-

This comment has been minimized.

Copy link

Billy- commented Nov 26, 2016

Not sure how I didn't see #1094 before but that has helped me resolve the issue. Particularly this comment. The problem was that my renderField function was inside the render function for the component.

@oyeanuj

This comment has been minimized.

Copy link
Contributor

oyeanuj commented Nov 30, 2016

(deleting my older comment as I got that issue to work.)

So, in case, anyone ends up here trying to make the comments above work for v6, with handleSubmit, here is the snippet that works for me.

//This example uses react-debounced-input but it can be any custom component instead.

renderDebouncedInput = (field) => {
  const { handleSubmit, onChangeSubmit } = this.props;

  return (
    <DebounceInput
      debounceTimeout = {500}
      onChange = {(event) => {
      
        /*
        call input.onChange before handleSubmit 
        if you are relying on the dirty flag in the update function
        and pass event.target.value not just event
        see #2197 (https://github.com/erikras/redux-form/issues/2197 
        - input.value is set to event when value is blank)
        */

        field.input.onChange(event.target.value); 
        handleSubmit((values, dispatch, props) => {
          const newValues = values;
          newValues[field.name] = event.target.value; //ensuring values obj has the latest value
          
          //Make sure to return the value of your update function 
          //so that SubmissionError doesn't get lost
          
          return onChangeSubmit(newValues, dispatch, props);
        })();
      }}
      value = {field.input.value}
    />
  );
};

Hope this helps someone!

@lhtdesignde

This comment has been minimized.

Copy link

lhtdesignde commented Dec 15, 2016

I'm struggling with this as well. I tried a few things suggested here but nothing worked so far. I have a custom select.

export const renderSelect = ({input, label, meta: {touched, error, submitting}}) => (
  <div className={classNames('type-select', {'x-error': touched && error})}>
    <label className='label'>{label}</label>
    <select {...input}
           disabled={submitting}
           aria-required={ariaRequired}
           className={classNames('select', {'x-error': touched && error})}>
      {options}
    </select>
  </div>
);

So now i want to submit my select as soon as the value changes but it seems not to submit anything.

renderOptions() {
    const {
      submitting,
      handleSubmit
    } = this.props;
return (
      <Field
        name='currency'
        component={renderSelect}
        ariaRequired='true'
        loadingClass={loadingClasses}
        options={options}
        onChange={event => {
          field.onChange(event);
          setTimeout(handleSubmit);
        }}/>
);
}

debugging it, field is not defined but it's not even acknowledging the onChange. How do I do this the v6 way?

@oyeanuj

This comment has been minimized.

Copy link
Contributor

oyeanuj commented Dec 15, 2016

@lhtdesignde You have to set up the onChange in your renderSelect function, like in my code example above. Initially, I was trying to do it like your code above, but that doesn't work. The onChange is available as field.input.onChange.

@lhtdesignde

This comment has been minimized.

Copy link

lhtdesignde commented Dec 16, 2016

great, that worked! onChangeSubmitAction just hands over handleSubmit.

export const renderSelect = ({input, label, onChangeSubmitAction, meta: {touched, error, submitting}}) => (
  <div className={classNames('type-select', {'x-error': touched && error})}>
    <label className='label'>{label}</label>
    <select {...input}
           disabled={submitting}
           aria-required={ariaRequired}
            onChange={event => {
              input.onChange(event.target.value);
              setTimeout(onChangeSubmitAction);
            }}>
      {options}
    </select>
  </div>
);
@egeste

This comment has been minimized.

Copy link

egeste commented Jan 30, 2017

Oh god. I need this.

@egeste

This comment has been minimized.

Copy link

egeste commented Jan 30, 2017

Pull request here #2530

@erikras

This comment has been minimized.

Copy link
Owner

erikras commented Mar 23, 2017

Fix published in v6.6.0.

@Abhijeetjaiswalit

This comment has been minimized.

Copy link

Abhijeetjaiswalit commented Aug 2, 2017

@oyeanuj @erikras is there any way to bind default value to react-select field? I' using following value property but it is coming blank on select side.:

Here is my select ;

             <Select
                   {...props}
                   value={field.input.value || ''}
                   onChange={(value) => field.input.onChange(value)}
                   options={field.options}
               />

here is my redux-form component:

                              <Field
                                      name="province"
                                      component={renderSelectFields.renderselectField}
                                      value={this.state.provinces}
                                      onChange={this.onProvinceChange.bind(this)}
                                      options={this.state.provinceOptions}
                                    />

can someone please help me?

@iOiurson

This comment has been minimized.

Copy link

iOiurson commented Aug 17, 2017

I have tried over and over again to apply this based on your examples, and it still does not work on my side (with v6.8.0).
Is it possible to have some proper documentation on this onChange() feature that allows the field to update before the form actually submits ?

@lock

This comment has been minimized.

Copy link

lock bot commented Aug 17, 2018

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Aug 17, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.