如何使用实现 useField() 的组件在多步骤 Formik 表单中设置值

How to set values in a multi-step Formik form with components that implement useField()

我正在将 multi-step wizard example with Material-UI components and it works well with the useField() hook but I cannot figure out how to bring setFieldValue() 实施到范围中,因此我可以从向导步骤中使用它。

我看到了使用 connect() higher-order component 的建议,但我不知道该怎么做。

这是我的代码片段:CodeSandbox,用例:

向导步骤有一些可选字段,可以 shown/hidden 使用 Material-UI Switch。我希望在关闭开关时清除可选字段中的值。

  1. 打开开关。
  2. 在评论字段中输入数据。
  3. 切换关闭。
  4. 评论值已清除。
  5. 打开开关。
  6. 评论字段为空。

希望有人能帮忙!谢谢。

我相信这就是您想要的:CodeSandbox。我分叉了你的 CodeSandbox。

我试图尽可能地遵循您的代码,但最终没有使用 WizardStepstep 变量返回一个 React 组件,它是 Formik 的子组件。 Formik 使用道具渲染,例如setFieldValue,可以传给子代。为了将 setFieldValue 作为道具传递给 step,我不得不使用 cloneElement() (https://reactjs.org/docs/react-api.html#cloneelement),这允许我克隆 step 组件和如下添加道具。

// FormikWizard.js
<Formik
    initialValues={snapshot}
    onSubmit={handleSubmit}
    validate={step.props.validate}
>
    {(formik) => (
        <Form>
            <DialogContent className={classes.wizardDialogContent}>
                <Stepper
                  className={classes.wizardDialogStepper}
                  activeStep={stepNumber}
                  alternativeLabel
                >
                  {steps.map((step) => (
                    <Step key={step.props.name}>
                      <StepLabel>{step.props.name}</StepLabel>
                    </Step>
                  ))}
                </Stepper>
                <Box
                  className={classes.wizardStepContent}
                  data-cy="wizardStepContent"
                >
                  {React.cloneElement(step, {
                      setFieldValue: formik.setFieldValue
                  })}
                </Box>
              </DialogContent>
              <DialogActions
                className={classes.wizardDialogActions}
                data-cy="wizardDialogActions"
              >
                <Button onClick={handleCancel} color="primary">
                  Cancel
                </Button>
                <Button
                  disabled={stepNumber <= 0}
                  onClick={() => handleBack(formik.values)}
                  color="primary"
                >
                  Back
                </Button>
                <Button
                  disabled={formik.isSubmitting}
                  type="submit"
                  variant="contained"
                  color="primary"
                >
                  {isFinalStep ? "Submit" : "Next"}
                </Button>
            </DialogActions>
        </Form>
    )}
</Formik>

为了访问子组件中的 setFieldValue 道具,在 App.js 中,我创建了一个名为 StepOne 的新组件,并用它来环绕输入,而不是使用 WizardStep。现在我可以访问 setFieldValue 并在 handleOptionalChange 函数中使用它。

// App.js
import React, { useState } from "react";
import "./styles.css";
import { makeStyles } from "@material-ui/core/styles";
import Box from "@material-ui/core/Box";
import CssBaseline from "@material-ui/core/CssBaseline";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormGroup from "@material-ui/core/FormGroup";
import Switch from "@material-ui/core/Switch";
import FormikTextField from "./FormikTextField";
import { Wizard, WizardStep } from "./FormikWizard";

const useStyles = makeStyles((theme) => ({
  content: {
    display: "flex",
    flexFlow: "column nowrap",
    alignItems: "center",
    width: "100%"
  }
}));

const initialValues = {
  forename: "",
  surname: "",
  optionalComments: ""
};

const StepOne = ({ setFieldValue }) => {
  const classes = useStyles();
  const [optional, setOptional] = useState(false);
  const displayOptional = optional ? null : "none";

  const handleOptionalChange = () => {
    setFieldValue("optionalComments", "");
    setOptional(!optional);
  };

  return (
    <Box className={classes.content}>
      <FormikTextField
        fullWidth
        size="small"
        variant="outlined"
        name="forename"
        label="Forename"
        type="text"
      />
      <FormikTextField
        fullWidth
        size="small"
        variant="outlined"
        name="surname"
        label="Surname"
        type="text"
      />
      <FormGroup>
        <FormControlLabel
          control={
            <Switch
              checked={optional}
              onChange={handleOptionalChange}
              name="optional"
              color="primary"
            />
          }
          label="Optional"
        />
      </FormGroup>
      <FormikTextField
        style={{ display: displayOptional }}
        fullWidth
        size="small"
        variant="outlined"
        name="optionalComments"
        label="Comments"
        type="text"
      />
    </Box>
  );
};

function App(props) {
  return (
    <>
      <CssBaseline />
      <Wizard
        title="My Wizard"
        open={true}
        initialValues={initialValues}
        onCancel={() => {
          return;
        }}
        onSubmit={async (values) => {
          console.log(JSON.stringify(values));
        }}
      >
        <StepOne />
        <StepTwo />
      </Wizard>
    </>
  );
}

export default App;

备选

要在 Formik 中使用 setFieldValue,最简单的方法是将所有输入元素放在 <Formik></Formik 标签中。您可以根据您正在进行的步骤有条件地呈现输入元素,如下所示。这使输入可以直接访问 setFieldValue,因此您可以在 Switch 输入上调用 setFieldValue("optionalComments", ""),这将清除每个切换的注释。虽然这可能意味着您的表格会更长,但我认为这不一定是坏事。

<Formik>
    <Form>
        {step === 1 && <div>
            // Insert inputs here
        </div>}

        {step === 2 && <div>
            <TextField 
                onChange={(event) => setFieldValue("someField", event.target.value)}
            />
             <Switch
              checked={optional}
              onChange={() => {
                  setFieldValue("optionalComments", "");
                  setOptional(!optional);
              }}
              name="optional"
              color="primary"
            />
        </div>}
    </Form>
</Formik>

前几天我遇到了这个 但由于无法正常工作而放弃了它。 它确实有效,但我对它是否是正确的方法有两种看法。

const handleOptionalChange = (form) => {
  setOptional(!optional)
  form.setFieldValue('optionalComments', '', false)
}
<FormGroup>
  <FormControlLabel
    control={
      // As this element is not a Formik field, it has no access to the Formik context.
      // Wrap with Field to gain access to the context.
      <Field>
        {({ field, form }) => (
          <Switch
            checked={optional}
            onChange={() => handleOptionalChange(form)}
            name="optional"
            color="primary"
          />
        )}
      </Field>
    }
    label="Optional"
  />
</FormGroup>

CodeSandbox.