我在 React Hook From 中使用 Yup 进行 MUI 自动完成验证时遇到问题。当我 concole.log
我的对象 (GrandLodge) 时,countryID
和 unionID
的道具总是 "0"
。
这是我的组件。
import { TextField, Typography } from "@material-ui/core";
import { Autocomplete } from "@material-ui/lab";
import React, { ChangeEvent } from "react";
import { Control, Controller, DeepMap, FieldError } from "react-hook-form";
import { Country, Union } from "./../../../service/client";
import { IBaseProps } from "./../Interfaces/BaseProps";
Country,
GrandLodge,
Lodge,
Union,
} from "./../../../service/client";
import { IBaseProps } from "./../Interfaces/BaseProps";
interface IHookFormSelectlistProps<T> extends IBaseProps {
name: string;
control: Control;
errors?: DeepMap<Record<string, any>, FieldError>;
disabled?: boolean;
data: Array<T>;
displayMember: (item: T) => string;
valueMember: (item: T) => string;
style?: React.CSSProperties;
selectedValue?: any;
onChange?: (item: T) => void;
label?: string;
className?: string;
value: T | null;
}
interface IHookFormSelectlistState {
setOpen: boolean;
}
type CustomSelect<T> = new (
props: IHookFormSelectlistProps<T>
) => HookFormSelectList<T>;
class HookFormSelectList<T> extends React.Component<
IHookFormSelectlistProps<T>,
IHookFormSelectlistState
> {
constructor(props: IHookFormSelectlistProps<T>) {
super(props);
this.state = {
setOpen: false,
};
}
private onInputChange = (
e: ChangeEvent<{}>,
value: string | T | null
): void => {
if (this.props.onChange) {
const item: T = this.props.data.toEnum().First((x: T) => x === value);
this.props.onChange(item);
}
};
render() {
const body = () => (
<React.Fragment>
<Controller
render={({ value, onBlur, onChange }) => (
<div className={this.props.className}>
<Autocomplete
freeSolo
fullWidth
openOnFocus={true}
onChange={(e: ChangeEvent<{}>, v: string | T | null) => {
onChange(e);
this.onInputChange(e, v);
}}
onOpen={() => this.setState({ ...this.state, setOpen: true })}
onClose={() => this.setState({ ...this.state, setOpen: false })}
getOptionSelected={(option, selected) => option === selected}
getOptionLabel={this.props.displayMember}
options={this.props.data}
loading={false}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
placeholder={this.props.label}
/>
)}
/>
</div>
)}
defaultValue={this.props.value}
name={this.props.name}
control={this.props.control}
onChangeName="onChange"
/>
{this.props.errors && this.props.errors[this.props.name] && (
<Typography variant="caption" color="error">
<small>
<span role="alert" id={`${this.props.name}Error`}>
{this.props.errors[this.props.name].message}
</span>
</small>
</Typography>
)}
</React.Fragment>
);
return body();
}
}
export const CountriesSelect: CustomSelect<Country> = HookFormSelectList;
export const UnionSelect: CustomSelect<Union> = HookFormSelectList;
import * as Yup from "yup";
export const validationSchema = Yup.object().shape({
name: Yup.string()
.required("This field is required.")
.min(2, "Lodge name has to be at last 2 charachters long.")
.max(255, "Name can't be longer then 255 charachters."),
countryId: Yup.string()
.required("This field is required.")
.min(0, "Country has to be selected."),
email: Yup.string()
.required("This field is required.")
.email("Not a valid e-mail adress."),
unionId: Yup.string()
.required("This field is required.")
.min(0, "Union has to be selected."),
pypalAccount: Yup.string()
.required("This field is required.")
.min(2, "Pypal account has to be at last 2 charachters long.")
.max(255, "Pypal account can't be longer then 255 charachters."),
cryptoWallet: Yup.string()
.required("This field is required.")
.min(2, "Crypto wallet has to be at last 2 charachters long.")
.max(255, "Crypto wallet can't be longer then 255 charachters."),
});
export interface GrandLodge {
time: Date;
timeStamp: Date;
readonly grandLodgeId: number;
unionId: number;
countryId: number;
name: string | undefined;
email: string | undefined;
pypalAccount: string | undefined;
cryptoWallet: string | undefined;
}
import { yupResolver } from "@hookform/resolvers/yup";
import {
Button,
Container,
CssBaseline,
Grid,
Typography,
} from "@material-ui/core";
import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import HookFormTextBox from "./../../../library/HookForm/Components/hookFomTextBox";
import { useStyles } from "./../../style.main";
import { validationSchema } from "./validationSchema";
import { Country, GrandLodge, Union } from "./../../../service/client";
import {
CountriesSelect,
UnionSelect,
} from "./../../../library/HookForm/Components/hookFormSelectlist";
import { withConnected } from "../../../state/withConnected";
import { WebAPI } from "./../../../service/webAPI";
interface IProps {
countries: Country[];
}
const GrandLodgePageBase = (props: IProps): JSX.Element => {
const [serverError, setServerError] = useState<string>("");
const [unions, setUnions] = useState<Union[]>([]);
useEffect(() => {
const fetchData = async (): Promise<void> => {
setUnions(await WebAPI.Union.getPage(0, 1000));
};
fetchData();
}, []);
const classes = useStyles();
const { control, handleSubmit, errors, formState } = useForm<GrandLodge>({
mode: "onChange",
resolver: yupResolver(validationSchema),
});
const onSubmit = async (data: GrandLodge): Promise<void> => {
if (!formState.isValid) {
return;
}
console.log(data);
};
return (
<Container component="main" maxWidth="xl">
<CssBaseline />
<Typography className={classes.title} variant="overline">
Add new grand lodge
</Typography>
<form
className={classes.form}
onSubmit={handleSubmit((data: GrandLodge) => onSubmit(data))}
>
<Grid container className={classes.root} spacing={2}>
<Grid item xs={12} sm={12} md={12} lg={6}>
<HookFormTextBox
name="name"
type="text"
label="Lodge Name"
control={control}
errors={errors}
className={classes.formElement}
/>
</Grid>
<Grid item xs={12} sm={12} md={12} lg={6}>
<HookFormTextBox
name="email"
type="email"
label="E-mail"
control={control}
errors={errors}
className={classes.formElement}
/>
</Grid>
</Grid>
<Grid container className={classes.root} spacing={2}>
<Grid item xs={12} sm={12} md={12} lg={6}>
<CountriesSelect
name="countryId"
label="Country"
displayMember={(x) => x.name!}
valueMember={(x) => x.countryId.toString()}
value={null}
data={props.countries}
control={control}
errors={errors}
className={classes.formElement}
/>
</Grid>
<Grid item xs={12} sm={12} md={12} lg={6}>
<UnionSelect
name="unionId"
label="Union"
displayMember={(x) => x.name!}
valueMember={(x) => x.unionId.toString()}
data={unions}
value={null}
control={control}
errors={errors}
className={classes.formElement}
/>
</Grid>
</Grid>
<Grid container className={classes.root} spacing={2}>
<Grid item xs={12} sm={12} md={12} lg={6}>
<HookFormTextBox
name="cryptoWallet"
type="text"
label="Crypto Wallet"
control={control}
errors={errors}
className={classes.formElement}
/>
</Grid>
<Grid item xs={12} sm={12} md={12} lg={6}>
<HookFormTextBox
name="pypalAccount"
type="text"
label="Pypal Account"
control={control}
errors={errors}
className={classes.formElement}
/>
</Grid>
</Grid>
<Grid item xs={12} sm={12} md={12} lg={12} justify="center" container>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.submit}
disabled={!formState.isValid}
>
SAVE
</Button>
</Grid>
<Grid item md>
<Typography variant="subtitle2" color="error" align="center">
{serverError}
</Typography>
</Grid>
</form>
</Container>
);
};
const GrandLodgePage = withConnected(GrandLodgePageBase, (s) => ({
countries: s.state.countries,
}));
export default GrandLodgePage;