【问题标题】:How can I create connected/dependent select elements in Formik?如何在 Formik 中创建连接/依赖的选择元素?
【发布时间】:2020-06-16 10:57:51
【问题描述】:

我有两个选择框,一个用于国家/地区,另一个用于地区。当有人选择一个国家时,我需要用不同的值(异步)填充区域选择。

我知道react-country-region-selector 和react-select,但是对于这样一个简单的任务,这些解决方案似乎有点过分了。

在下面的代码中,区域在选择国家后正确填充,但是国家选择的值丢失了。另外,我应该在构造函数中设置状态还是 Formik 应该处理所有状态?

import React from 'react';
import { Formik, Form, Field } from "formik";

class App extends React.Component {
  constructor(props) {
    super(props);

    console.log(`props: ${JSON.stringify(props, null, 2)}`)

    this.state = {
      regions: []
    }

    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleCountryChanged = this.handleCountryChanged.bind(this);
    this.getRegions = this.getRegions.bind(this);
  }

  handleSubmit(values, { setSubmitting }) {
    console.log(JSON.stringify(values), null, 2);
  };

  handleCountryChanged(event) {
    const country = event.target.value;
    this.getRegions(country).then(regions => {
      this.setState({ regions: regions });
      console.log(`regions: ${JSON.stringify(regions, null, 2)}`);
    });
  }

  getRegions(country) {
    // Simulate async call
    return new Promise((resolve, reject) => {
      switch (country) {
        case "United States":
          resolve([
            { value: 'Washington', label: 'Washington' },
            { value: 'California', label: 'California' }
          ]);
          break;
        case "Canada":
          resolve([
            { value: "Alberta", label: "Alberta" },
            { value: "NovaScotia", label: "Nova Scotia" }
          ]);
          break;
        default:
          resolve([]);
      }
    });
  }

  render() {
    return (
      <Formik
        initialValues={{ country: "None", region: "None", regions: [] }}
        onSubmit={this.handleSubmit}
      >
        {({ isSubmitting }) => (
          <Form>
            <label htmlFor="country">Country</label>
            <Field id="country" name="country" as="select"
              onChange={this.handleCountryChanged}>
              <option value="None">Select country</option>
              <option value="United States">United States</option>
              <option value="Canada">Canada</option>
            </Field>
            <label htmlFor="region">Region</label>
            <Field id="region" name="region" as="select">
              <option value="None">Select region</option>
              {this.state.regions.map(r => (<option key={r.value} value={r.value}>{r.label}</option>))}
            </Field>
            <button type="submit" disabled={isSubmitting}>Submit</button>
          </Form>
        )}
      </Formik>);
  }
}

export default App;```

【问题讨论】:

    标签: reactjs formik


    【解决方案1】:

    您可以使用 Formik 的 useField 和 useFormikContext 挂钩将一个字段的值设置为依赖于另一个字段。文档here 中有一个演示;这是一个简化的例子:

    const DependentField = (props: FieldAttributes<any>) => {
        const { values, touched, setFieldValue } = useFormikContext<ValueType>() // get Formik state and helpers via React Context
        const [field, meta] = useField(props) // get the props/info necessary for a Formik <Field> (vs just an <input>)
    
        React.useEffect(() => {
            // set the values for this field based on those of another
            switch (values.country) {
                case 'USA':
                    setFieldValue(props.name, 'Asia')
                    break
                case 'Kenya':
                    setFieldValue(props.name, 'Africa')
                    break
                default:
                    setFieldValue(props.name, 'Earth')
                    break
            }
        }, [values.country, touched, setFieldValue, props.name]) // make sure the component will update based on relevant changes
    
        return (
            <>
                <input {...props} {...field} />
                {!!meta.touched && !!meta.error && <div>{meta.error}</div>}
            </>
        )
    }
    
    // then, use it in your form.
    const MyForm = (props: any) => {
        // do stuff
        return(
            <Formik>
                <Field name="country">
                    // options
                </Field>
                <DependentField name="region"> // this field will now change based on the value of the `country` field.
                    // options
                </DependentField>
            </Formik>
        )
        
    }
    
    

    警告 - 不要尝试在确定更改的字段上使用 onChange 执行此操作。这可能看起来很直观,但在此过程中存在许多问题,主要是 Formik 的handleChange 必须触发该字段才能正常工作;但它是异步的,但不返回任何承诺,因此不会获取处理程序中的其他更改。

    【讨论】:

      【解决方案2】:

      我认为你应该处理 get 区域并在 formik 中设置

      这里是示例代码(codesanbox):

      Formik handle get regions

      代码在这里:

      // Helper styles for demo
      import "./helper.css";
      import { MoreResources, DisplayFormikState } from "./helper";
      
      import React from "react";
      import { render } from "react-dom";
      import { Formik, Field } from "formik";
      import * as Yup from "yup";
      
      const App = () => {
        const getRegions = country => {
          // Simulate async call
          return new Promise((resolve, reject) => {
            switch (country) {
              case "United States":
                resolve([
                  { value: "Washington", label: "Washington" },
                  { value: "California", label: "California" }
                ]);
                break;
              case "Canada":
                resolve([
                  { value: "Alberta", label: "Alberta" },
                  { value: "NovaScotia", label: "Nova Scotia" }
                ]);
                break;
              default:
                resolve([]);
            }
          });
        };
      
        return (
          <div className="app">
            <h1>
              Basic{" "}
              <a
                href="https://github.com/jaredpalmer/formik"
                target="_blank"
                rel="noopener noreferrer"
              >
                Formik
              </a>{" "}
              Demo
            </h1>
      
            <Formik
              initialValues={{ country: "None", region: "None", regions: [] }}
              onSubmit={async values => {
                await new Promise(resolve => setTimeout(resolve, 500));
                alert(JSON.stringify(values, null, 2));
              }}
              validationSchema={Yup.object().shape({
                email: Yup.string()
                  .email()
                  .required("Required")
              })}
            >
              {props => {
                const {
                  values,
                  dirty,
                  isSubmitting,
                  handleChange,
                  handleSubmit,
                  handleReset,
                  setFieldValue
                } = props;
                return (
                  <form onSubmit={handleSubmit}>
                    <label htmlFor="country">Country</label>
                    <Field
                      id="country"
                      name="country"
                      as="select"
                      value={values.country}
                      onChange={async e => {
                        const { value } = e.target;
                        const _regions = await getRegions(value);
                        console.log(_regions);
                        setFieldValue("country", value);
                        setFieldValue("region", "");
                        setFieldValue("regions", _regions);
                      }}
                    >
                      <option value="None">Select country</option>
                      <option value="United States">United States</option>
                      <option value="Canada">Canada</option>
                    </Field>
                    <label htmlFor="region">Region</label>
                    <Field
                      value={values.region}
                      id="region"
                      name="region"
                      as="select"
                      onChange={handleChange}
                    >
                      <option value="None">Select region</option>
                      {values.regions &&
                        values.regions.map(r => (
                          <option key={r.value} value={r.value}>
                            {r.label}
                          </option>
                        ))}
                    </Field>
      
                    <button
                      type="button"
                      className="outline"
                      onClick={handleReset}
                      disabled={!dirty || isSubmitting}
                    >
                      Reset
                    </button>
                    <button type="submit" disabled={isSubmitting}>
                      Submit
                    </button>
      
                    <DisplayFormikState {...props} />
                  </form>
                );
              }}
            </Formik>
      
            <MoreResources />
          </div>
        );
      };
      
      render(<App />, document.getElementById("root"));
      

      【讨论】:

        猜你喜欢
        • 2019-05-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-06
        • 1970-01-01
        • 2015-04-09
        • 1970-01-01
        • 2019-02-03
        相关资源
        最近更新 更多