Skip to content
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

Question: Async validation with redux-saga #655

Closed
tristangodfrey opened this issue Feb 17, 2016 · 21 comments
Closed

Question: Async validation with redux-saga #655

tristangodfrey opened this issue Feb 17, 2016 · 21 comments
Labels

Comments

@tristangodfrey
Copy link

I've been stuck trying to think this out for a while now.

I have a saga that catches API_REQUEST actions, that in turn executes the request and sets validationErrors on my state if there are any. How can I invalidate a redux-form with this data?

@erikras
Copy link
Member

erikras commented Feb 17, 2016

Hmm... I've been watching redux-saga with interest, but this library is fairly promise-based. Both submit and async errors are returned with promises.

@tristangodfrey
Copy link
Author

tristangodfrey commented Feb 17, 2016

I'm quite a beginner when it comes to the whole redux scene but the idea I'm struggling with is the concept of an action having a return value. I'm pretty sure this is what redux-form expects when I call an action on submit, but I don't think I'm able to implement this with a saga.

If it helps at all this is what my saga looks like, should be pretty easy to tell what it does;

function* sendRequest(token) {

  while (true) {
    //Wait for an API call
    const callAPI = yield take(action => action.CALL_API);

    let { endpoint, data } = callAPI;
    const { types } = callAPI;
    const [ requestType, successType, failureType ] = types;

    //Dispatch the request_started action
    yield put(formatAPIActionToDefaultAction({ type: requestType }));
    //Dispatch action to display loader
    yield put(displayLoader());

    const response = yield call(callApi, endpoint, data, token);
    //Response is done, hide loader
    yield put(hideLoader());

    if(response.hasOwnProperty("error")) {
      //Request failed
      yield put(formatAPIActionToDefaultAction({ type: failureType }));
      yield parseAPIError(response.type, response.error);
      //This sets either validationErrors or generalError on my state

      //Check for a failure route
      if (callAPI => callAPI.failureRoute) {
        Actions[callAPI.failureRoute](); //Call it
      }
    } else {
      //Request success!
      yield put(formatAPIActionToDefaultAction({ type: successType, response: response.json }));
      //Check for a success route
      if (callAPI => callAPI.successRoute) {
        Actions[callAPI.successRoute](); //Call it
      }
    }

    //Set token for next request
    //If token expired it will now be false, this will trigger a redirect to
    //login
    yield put(setToken(response.token));

  }

}

@mattiamanzati
Copy link

I have been working with that, and my solution is to disable (monkey patch) the automatic dispatch of stop_submit.
In My saga I listen form start_submit, perform the http request and manually dispatch stopSubmit with the errors.
My suggestion is that a prop (or when onSubmit is not provided) redux-form Will only dispatch the start_submit, and its up to the User dispatch the stopsubmit from a saga.

@mattiamanzati
Copy link

Uh, sorry, did not mentioned the validation. I manually do it in the saga, and handle the submit.

@tristangodfrey
Copy link
Author

@mattiamanzati Makes sense, thanks for the push in the right direction!

@mattiamanzati
Copy link

@erikras Should we consider about adding a behaviour where if onSubmit is not defined redux-form will only dispatch the START_SUBMIT action?
This will reduce the boilerplate and make the component more "redux-ish".

@erikras
Copy link
Member

erikras commented Feb 17, 2016

@mattiamanzati Hmm... I'm not sure. As it is, it will only dispatch the START_SUBMIT (and STOP_SUBMIT) action if onSubmit() returns a Promise; otherwise, it is assumed to be a synchronous operation.

@mattiamanzati
Copy link

Uhm... I don't know, I personally love to listen to redux-form's action, but tbh let the user manually dispatch it's own submit action and listen to that maybe it's better on long life application, as you could change the underlaying form package with almost no changes.

@tristangodfrey
Copy link
Author

@mattiamanzati

I have been working with that, and my solution is to disable (monkey patch) the automatic dispatch of stop_submit.

Could you provide an example of how you did this?

@mohebifar
Copy link

@tristangodfrey

Could you provide an example of how you did this?

You can manually dispatch stopSubmit.

import { stopSubmit } from 'redux-form';

// ...

try {
  const response = yield apiCall();
  // ...
} catch (response) {
  const errors = ...;
  yield put(stopSubmit('FORM_NAME', errors));
}

@danturu
Copy link

danturu commented Feb 29, 2016

@erikras I agree with @mattiamanzati

Should we consider about adding a behaviour where if onSubmit is not defined redux-form will only dispatch the START_SUBMIT action?

onSubmit: (...) => {
  setTimeout(() => {
    // Dispatch action;
  });

  // Returns never resolved promise...

  return new Promise(() => {  / *Later dispatch stopSubmit from a saga. */  });
}

With redux-saga we need a way to force redux-form only dispatch the START_SUBMIT action. Something like:

onSubmit: (...) => {
  // Dispatch action... Later dispatch stopSubmit from a saga.
  return START_ASYNC;
}

https://github.com/erikras/redux-form/blob/master/src/handleSubmit.js#L11

@danturu
Copy link

danturu commented Feb 29, 2016

Or even better:

onSubmit: (...) => {
  // Dispatch action... Dispatch startSubmit/stopSubmit from a saga.
  return ON_SUBMIT; //  { type: "redux-form/ON_SUBMIT", form: "form"} }
}

@Anahkiasen
Copy link

I'm using @mohebifar's solution (which works very well) but it does cause onSubmitSuccess to trigger even if the saga then dispatches stopSubmit which is problematic

@danturu
Copy link

danturu commented May 30, 2016

@Anahkiasen I don't remember all the details, but take a look at this solution redux-saga/redux-saga#161 (comment)

@Anahkiasen
Copy link

@rosendi Worked like a charm, thanks!

@Alxandr
Copy link

Alxandr commented Oct 6, 2016

I don't want to involve promises at all (cause microtasks can bite you in the ass depending on how you use redux-saga). Also, I don't want functions (resolve, reject) in my actions. Would it be possible to simply get a config value for reduxForm that disables invoking SET_SUBMIT_SUCCEEDED immediately after onSubmit?

[Edit]
As an alternative, I could not use handleSubmit at all, but then I lose the built in validation which I don't want. The same goes for async validation btw. It would be really nice to enable a non-promise mode where I have to manually call the _END actions.

@voy
Copy link

voy commented Feb 14, 2017

Any progress on finding out a better solution for this one, anyone? Returning a promise which never resolves seems a bit hacky and memory-leaky to me. On the other hand, handling all the async validation business myself using start/stop async validation is possible and very flexible, but it feels like I would have to reimplement a lot of the current behavior myself - inevitably introducing bugs in the process. So is the unresolved promise maybe the best way after all?

Agreed with @Alxandr that having the ability to opt out of using promises at all would probably be the best solution.

@trefnis
Copy link

trefnis commented May 8, 2017

I went with quite different way of solving this - instead of dispatching any API_REQUEST-like action, I am dispatching spawnSaga(sagaToBeRun, args) and intercept it with middleware, that just runs given saga with calling and returning sagaMiddleware.run. As the result is saga's task, you can use its .done property, which is a promise itself, so it is possible to just throw bare SubmissionErrors from sagas body, as .done will be basically rejected with these.

@danielrob
Copy link
Collaborator

Suggesting this get's added to the documentation along with #3235.

@gustavohenke
Copy link
Collaborator

Because this issue saw almost no activity for a few months, I'm closing it.

@lock
Copy link

lock bot commented Dec 7, 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 Dec 7, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests