如何在 Formik 中创建 connected/dependent select 元素?
How can I create connected/dependent select elements in Formik?
我有两个 select 框,一个用于国家,另一个用于区域。当某人 select 是一个国家时,我需要用不同的值(异步)填充区域 select。
我知道 react-country-region-selector and react-select,但这些解决方案对于这样一个简单的任务来说似乎有点过分了。
在下面的代码中,在 select 一个国家之后正确填充了区域,但是国家 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;```
我认为你应该在 formik 中处理获取区域和设置
这里是示例代码(codesanbox):
代码在这里:
// 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"));
您可以使用 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
必须开火才能使该字段正常工作;但它是异步的,但 returns 没有承诺,因此不会在处理程序中获取其他更改。
我有两个 select 框,一个用于国家,另一个用于区域。当某人 select 是一个国家时,我需要用不同的值(异步)填充区域 select。
我知道 react-country-region-selector and react-select,但这些解决方案对于这样一个简单的任务来说似乎有点过分了。
在下面的代码中,在 select 一个国家之后正确填充了区域,但是国家 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;```
我认为你应该在 formik 中处理获取区域和设置
这里是示例代码(codesanbox):
代码在这里:
// 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"));
您可以使用 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
必须开火才能使该字段正常工作;但它是异步的,但 returns 没有承诺,因此不会在处理程序中获取其他更改。