How to handle form state with Formik and Redux-Saga
Asked Answered
S

2

10

I recently started using Redux-Saga in a React project since it was new to me and I wanted to learn how it works. I also started using Formik since it appears to have eclipsed Redux-Form in popularity for managing forms in React applications. Now, I understand Dan Abramov's rationale to "use React for ephemeral state that doesn't matter to the app globally and doesn't mutate in complex ways."

But this seems at odds with the pattern of SOMETHING_REQUESTED -> apiCall -> SOMETHING_SUCCESS or SOMETHING_FAILURE laid out in Redux-Saga's documentation. For example, if I have a form that dispatches some action onSubmit that a saga "takes" to perform the asynchronous request, I don't see a way to keep my form apprised of the status of that request without storing it in Redux state somewhere (the exact antipattern we want to avoid). One alternative I could imagine is to perform the request within the form submission handler and not delegate it to a saga, but then that makes me wonder, what is the point of Redux-Saga?

Please help fill in the gaps in my understanding.

Edit: FYI this GitHub issue on the redux-saga repo seems most pertinent to my question, and while there is much written there it doesn't seem to arrive at an agreed best practice.

This reddit thread also deals with this topic, but similar to the thread's OP, I wonder why the solution he found in Redux Promise Listener is not more widely adopted?

Sensible answered 19/3, 2019 at 12:5 Comment(0)
B
3

One alternative I could imagine is to perform the request within the form submission handler and not delegate it to a saga

Correct, this is enough for handling file upload. Using redux-saga for this operation would be an over-kill. I believe that one should strive to pick the correct tool for a given task.

but then that makes me wonder, what is the point of redux-Saga?

Consider redux-saga as a "process manager" for making async operation as sync. Sometimes the flow of code is expected and there wont be any surprises. Although sometimes it doesn't so, since there is inversion of control caused by side effects (e.g API calls). Therefore, causing surprises and uncertainty about when we will get our response.

I don't see a way to keep my form apprised of the status of that request without storing it in Redux state somewhere

In this Demo I'm using redux-saga together with Formik in order to control 2 things:

  1. API call for POSTing a file to an endpoint.
  2. uploading flag for controlling the UI while the user is waiting for the file to be uploaded

redux handles only uploading, whereas the rest of the values are handled by formik and redux-saga that helps to provide a better user-experience (the user have an uploading indicator for what is happening in the UI) and developer-experience (we are controlling the flow of executionin the saga by yielding the response).

Bergin answered 20/5, 2019 at 11:32 Comment(4)
Thank you for your response! I took a look through the demo and I don't really see the value that Formik provides in this case. You pass in a submit handler but the component you wrap doesn't seem to make use of it. Can you please explain?Sensible
Explaining the values that formik brings to the table in this case would be off this question scope.. You can read about Formik here. About the submit handler, you can read about passing props to wrapped component. Please note: in the demo handleSubmit uses props.uploadFile(values) which is then used in handleUpload. The flow of props is bit hard to comprehend at the beginning, but eventually it links together :)Bergin
Link for passing props to wrapped component withFormikBergin
Okay, perhaps my misunderstanding stems from unfamiliarity with antd, but per my understanding withFormik serves to pass props into a component that contains a form. In the case of your demo, I do not see a form element or the submit handler being used. Plus, I don't see you using the formik helpers like setSubmitting based off the props you receive from the redux store. i.e., my question in my comment is not to ask how Formik works, but if Formik is needed at all in your demo.Sensible
G
0

What I have done is to send formik actions to saga and handle the form actions based on saga API call response.

This is my formik form representational component:

import React from "react";
import { ErrorMessage, Field, Form, Formik } from "formik";
import * as Yup from "yup";
import { Button, Col, FormGroup } from "reactstrap";

const RefundForm = (props) => {
  return (
    <Formik
      initialValues={{
        bank: "",
        amount: "",
      }}
      validationSchema={Yup.object({
        // availableEmoney: Yup.string().required("Required"),
        // availableLimit: Yup.string().required("Required"),
        bank: Yup.string().required("Required"),
        amount: Yup.string().required("Required"),
      })}
      onSubmit={props.handleSubmitMethod}
    >
      {(formikProps) => (
        <Form onSubmit={formikProps.handleSubmit}>
          <div className="form-row">
            <Col lg={8} xl={8}>
              <FormGroup>
                <label>Bank Name & Account Number</label>
                <Field
                  as="select"
                  name="bank"
                  className="form-control"
                >
                  <option value="">Select a bank</option>
                  {props.banks.map((bank) => {
                    return (
                      <option value={bank.id} key={bank.id}>
                        {`${bank.bank_name} (${bank.bank_account_number})`}
                      </option>
                    );
                  })}
                </Field>
                <ErrorMessage
                  name="bank"
                  component="div"
                  className="text-danger"
                />
              </FormGroup>
            </Col>

            <Col lg={4} xl={4}>
              <FormGroup>
                <label>Amount</label>
                <Field name="amount" type="text" className="form-control" />
                <ErrorMessage
                  name="amount"
                  component="div"
                  className="text-danger"
                />
              </FormGroup>
            </Col>
          </div>
          <div className="form-row mt-3 text-right">
            <Col>
              <Button
                className="primary-color"
                type="submit"
                disabled={!formikProps.dirty || formikProps.isSubmitting}
              >
                Submit
              </Button>
            </Col>
          </div>
        </Form>
      )}
    </Formik>
  );
};


export default RefundForm;

This is my function that handles the formik form submit:

  const handleRefundFormSubmit = (values, actions) => {
    //values: form values. actions: formikActions 

    dispatch(
      createRefund({
        data: {
          amount: values.amount,
          distributor_bank_id: values.bank,
        },
        formikActions: actions, // sending formik actions to the saga
      })
    );
    actions.setSubmitting(false);
  };

This is my saga function:

function* createNewRefund(action) {
  try {
    const response = yield call(
      postDataWithAuth,
      REFUND_CREATE,
      action.payload.data
    );
    yield put(createRefundSuccessful(response.message));
// Call the formik resetForm() function if the response is success so that the 
// the form is cleared
    yield put(action.payload.formikActions.resetForm());
  } catch (error) {
    yield put(createRefundFailed(error.response.data));
  }
}
Gangster answered 27/9, 2020 at 9:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.