React-final-form: Provide a set of callbacks to onSubmit method

Created on 17 Dec 2017  路  13Comments  路  Source: final-form/react-final-form


Provide a set of callbacks to onSubmit method, for example reset function

Are you submitting a bug report or a feature request?

feature request

What is the current behavior?

<Form
  onSubmit={onSubmit}
  initialValues={{ employed: true }}
  render={({ handleSubmit, reset }) => (
    <form
      onSubmit={event => {
        handleSubmit(event).then(() => {
          reset() // or could be passed directly to then()
        })
      }}
    >
    ...
    </form>
  }/>

What is the expected behavior?

const onSubmit = (values, reset) =>  {
    return new Promise (resolve => {
        reset();
        resolve();
    }
};
<Form
  onSubmit={onSubmit}
  initialValues={{ employed: true }}
  render={({ handleSubmit, reset }) => (
    <form  onSubmit={handleSubmit}>
    ...
    </form>
  }/>

Most helpful comment

Oh of course, yes, I mean that I'd put one together. Hopefully early next week!

All 13 comments

It _could_ do this, but you already have a decent option available. I鈥檓 not sure we should provide six ways to do everything.

@erikras As mentioned in #21, you dont always have a luxury to use promises, for example when using redux-saga. What I am forced to do is below code:

let resetForm;

class EditUserDataForm extends Component {
  handleSubmit = (values, setErrors) => {
    this.props.editUser(values, setErrors, resetForm);
  };

  render() {
    return (
      <Form
        onSubmit={this.handleSubmit}
        render={({ handleSubmit, reset }) => (
          <form
            onSubmit={(e) => {
              resetForm = reset;
              handleSubmit(e);
            }}
          >
            ...
          </form>
    )}
      />
    );
  }
}

which I really don't like. I could also do this.resetForm = reset, but still, I just feels like a hack because I don't have access to form wide actions in onSubmit, whereas this is the place where those actions belong. We have an access to form values in Form.onSubmit, and often we also need some form mutators, like setValidationError (which we already have, but only to this one, which is not consistent), reset, blur, change. And now, we need to create this bridge in form.onSubmit. What I recommend is the following:

<Form
  onSubmit={(values, { reset, setSubmitError, focus, blur, change, batch, mutators }) => {
    ...
  }}
  ...
</Form>

where setSubmitError is the old callback. This is more or less how Formik does this, which allows so much flexibility where this is often so needed - during the form submit.

@erikras how do you propose to handle callbacks when not disabling your submit on invalid? handleSubmit.then(reset) will not work if you never get to your onSubmit function to return the Promise.

@stunaz I wouldnt just pass reset to the onSubmit function, rather a set of actions (ie: formikBag)

I wouldnt just pass reset to the onSubmit function, rather a set of actions

Yes, this is obvious. The whole FormApi could be provided, even.

Fixed published in [email protected] and [email protected].

I came here with almost the same issue as @klis87, and thought I'd post my implementation using the FormAPI that's now available in the callback.

My form:

class LoginForm extends React.Component<Props> {
   handleLogin = (credentials: Credentials, formAPI): void => {
      const { reset } = formAPI
      this.props.loginUser({ credentials, failureCallback: reset })
   }

   render() {
      return (
         <Form
            // ... Omitted for simplicity, nothing fancy here
         />
      )
   }
}

My Sagas:

function* loginSaga(params: { credentials: Credentials, onFailure: () => void, onSuccess: () => void }): Saga<void> {
   try {
      // authenticateUser is a basic AJAX call that resolves on a successful login, rejects with bad credentials
      const user = yield call(authenticateUser, params.credentials)
      if (params.onSuccess) yield call(params.onSuccess)
      yield put({ type: LOGIN_SUCCESS, payload: user })
   } catch (error) {
      if (params.onFailure) yield call(params.onFailure)
      yield put({ type: LOGIN_FAILURE, payload: { loginError: error.message } })
   }
}

I'm so used to promises that callbacks feel 'hacky' at this point, but I can't figure out a more elegant way to work with Redux.

@klis87, I'd be curious to see how you ended up doing this if you have any insights!

@erikras I'd second the request to have some kind of setSubmitError method within the Form API (or even a setErrors method that can be used on fields as well). I'm guessing that this would be an issue in the main final-form repo, correct? Or, do you have any suggested ways to handle this?

@good-idea I did it in similar way like you, just passing callbacks to redux actions. Passing callback is required due to how redux-saga works, final-form cannot help you with this, because you cannot know in component when a saga execution is finished - action wont return promise like in case of redux-thunk. For me this is not so much of a deal, but unfortunately saga and redux needs to use those provided form callbacks which really dont belong to redux, but to react and UI. As an alternative, see this library https://github.com/diegohaz/redux-saga-thunk , I didnt use it, but I might consider to check it out at some point.

Passing callback is required due to how redux-saga works, final-form cannot help you with this, because you cannot know in component when a saga execution is finished

Final Form _does_ allow passing a callback. However, I don't know enough about Redux-Saga to know if that helps, but it _is_ sort of the equivalent of having a setSubmitError.

Fantastic, thanks to both of you.

@erikras would it be helpful to make a Final Form + Redux-Saga example?

It _would_! But I'm not the one to do it. I'd be glad to link to it in the README.

Oh of course, yes, I mean that I'd put one together. Hopefully early next week!

Another way to do it is to wrap actual action in promise and send resolve/reject with it.

submit(values) {
  return new Promise((resolve, reject) => {
    updateAction({ values, resolve, reject });
  });
}

Then in saga you can call yield call(resolve, ...) or reject.
So promises will be there and it will be possible to use .then(reset).

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.

Was this page helpful?
0 / 5 - 0 ratings