使用 Formik (React) 更新 Material-UI TextField onBlur
Updating a Material-UI TextField onBlur with Formik (React)
我目前正在呈现一个 editable table,它允许用户一次批量编辑多个用户的信息(见图)。我正在使用 Material-UI 的 <TextField/>
和 Formik 来处理表单的提交和状态。
我正在努力:
- 保持
<TextField />
的值和 Formik 的状态同步
- 每当我删除一行时(单击 x 时),以反映整个变化 table。
此 table 通常包含大约 266 个输入字段。使用 onChange
事件会带来严重的性能问题。因此,我不得不应用多个组件包装和记忆,以防止每次单个输入更改时重新呈现所有输入字段。
我已经成功地完成了这项工作(几乎以一种良好的性能方式),除了每当我删除一行时。旧值似乎仍然存在,而 Formik 的值确实发生了变化。
问题似乎在于 <TextField />
的 defaultValue
和 value
属性如何工作。
value
属性 似乎创建了一个受控组件,并且会一对一地反映您在其中传递的任何值。我试过将 Formik 的 field.value
直接设置到字段中。不幸的是,该值不会更新该字段,因为我目前正在使用 onBlur 事件来执行此操作(并且永远不会显示更改)。如果我要使用 onChange,一切都会正常,除了性能会很差,因为它会更新所有字段。
另一方面,defaultValue
使组件不受控制。尽管如此,我还是可以编辑该值,甚至可以更新 Formik 的状态 onBlur
!。 有一个问题,但是...每当我删除一行时,<TextField/>
中的值不会更新(但是Formik 是否反映了变化)。
似乎 <TextField />
组件内部正在进行一些缓存,因为我已经尝试记录该字段的值,这是我当前传递给 defaultValue
的值,它 正在显示 变化。
我也试过:
- 修补
defaultValue
和 value
- 设置一个
useState
挂钩作为 Formik 值和组件值之间的中间人
- 正在删除备忘。
- 手动实现记忆比较。
其中 none 似乎有效...在这种情况下我该怎么办?
作为参考,这是我使用的代码:
这是我当前使用的文本字段:
FormText
import React, { memo } from 'react';
import { useField } from 'formik';
import TextField from '@material-ui/core/TextField';
import { TextProps } from '../../../Fields/TextField/textfield-definitions';
type ComponentProps = TextProps & {
useBlur?: boolean;
errorMessage: string | undefined;
};
export const Component: React.FC<ComponentProps> = memo(props => {
const {
className,
name,
label,
placeholder,
required,
useBlur,
error,
errorMessage,
onChange,
onBlur,
value,
} = props;
// We wrap it so we don't block the heap stack!
// Improves performance considerably
// https://medium.com/trabe/react-syntheticevent-reuse-889cd52981b6
const fireBlur = (e: any) => {
// React removes
e.persist();
window.setTimeout(() => {
if (onBlur) {
onBlur(e);
}
}, 0);
};
const setInnerState = (e: React.ChangeEvent<HTMLInputElement>) => {};
const fireChange = (e: React.ChangeEvent<HTMLInputElement>) => {
e.persist();
setInnerState(e);
window.setTimeout(() => {
if (onChange) {
onChange(e);
}
}, 0);
};
return (
<TextField
className={className}
name={name}
label={label}
type={props.type}
placeholder={placeholder}
defaultValue={value}
variant="outlined"
required={required}
error={error}
helperText={<span>{error ? errorMessage : ''}</span>}
onChange={useBlur ? undefined : fireChange}
onBlur={useBlur ? fireBlur : undefined}
/>
);
});
export const SchonText: React.FC<TextProps> = props => {
const [field, meta] = useField(props.name);
const hasError = !!meta.error && !!meta.touched;
return (
<Component
value={field.value}
{...props}
error={hasError}
errorMessage={meta.error}
onChange={field.onChange}
onBlur={field.onChange}
/>
);
};
export default SchonText;
以下是使用它的组件:
TableRow
import React, { memo } from 'react';
import { TableRow, TableCell, makeStyles } from '@material-ui/core';
import { Close } from '@material-ui/icons';
import {
FormText,
FormSelect,
FormTextArea,
Button,
} from '../../../../../../components';
import { Student, Gender } from '../../../../../../graphql/types';
import { SelectOption } from '../../../../../../components/Fields/Select/select-definitions';
type BulkAddTableRowProps = {
student: Student;
index: number;
deleteStudent: (index: number) => void;
};
const useStyles = makeStyles(theme => ({
root: {
padding: `0px`,
},
}));
const selectOptions: SelectOption[] = [
{
label: 'M',
value: Gender.Male,
},
{
label: 'F',
value: Gender.Female,
},
];
const Component: React.FC<BulkAddTableRowProps> = props => {
const styles = useStyles();
const { student, index } = props;
const deleteStudent = () => props.deleteStudent(index);
return (
<TableRow className={styles.root} hover={true}>
<TableCell>{index + 1}</TableCell>
<TableCell className={styles.root}>
<FormText
name={`students[${index}].name.firstName`}
value={student.name.firstName}
useBlur={true}
/>
</TableCell>
<TableCell>
<FormText
name={`students[${index}].name.lastName`}
value={student.name.lastName}
useBlur={true}
/>
</TableCell>
<TableCell>
<FormSelect
name={`students[${index}].gender`}
value={student.gender}
options={selectOptions}
/>
</TableCell>
<TableCell>
<FormText
type="email"
name={`students[${index}].email`}
value={student.email}
useBlur={true}
/>
</TableCell>
<TableCell>
<FormText
type="date"
name={`students[${index}].birthDate`}
value={student.birthDate}
useBlur={true}
/>
</TableCell>
<TableCell>
<FormTextArea
name={`students[${index}].allergies`}
value={student.allergies}
useBlur={true}
/>
</TableCell>
<TableCell>
<FormTextArea
name={`students[${index}].diseases`}
value={student.diseases}
useBlur={true}
/>
</TableCell>
<TableCell>
<Button onClick={deleteStudent}>
<Close />
</Button>
</TableCell>
</TableRow>
);
};
function shouldRemainTheSame(
prevProps: BulkAddTableRowProps,
newProps: BulkAddTableRowProps,
): boolean {
const prevStudent = prevProps.student;
const newStudent = newProps.student;
const isNameTheSame = Object.keys(prevStudent.name).every(key => {
return prevStudent.name[key] === newStudent.name[key];
});
const isStudentTheSame = Object.keys(prevStudent)
.filter(x => x !== 'name')
.every(key => prevStudent[key] === newStudent[key]);
return (
isNameTheSame && isStudentTheSame && prevProps.index === newProps.index
);
}
export const BulkAddTableRow = memo(Component, shouldRemainTheSame);
export default BulkAddTableRow;
StudentBulkTableView
import React, { memo } from 'react';
import {
FieldArray,
FieldArrayRenderProps,
getIn,
useFormikContext,
} from 'formik';
import { Student, Gender } from '../../../../graphql/types/index';
import {
Paper,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
makeStyles,
} from '@material-ui/core';
import { Button, Select } from '../../../../components';
import { SelectOption } from '../../../../components/Fields/Select/select-definitions';
import { emptyStudent, BulkAddStudentValues } from '../shared';
import BulkAddTableRow from './components/TableRow/index';
type ComponentProps = {
push: (obj: any) => void;
remove: (index: number) => undefined;
students: Student[];
setFieldValue: (
field: 'students',
value: any,
shouldValidate?: boolean | undefined,
) => void;
};
const selectOptions: SelectOption[] = [
{
label: 'M',
value: Gender.Male,
},
{
label: 'F',
value: Gender.Female,
},
];
const useStyles = makeStyles(theme => ({
root: {
padding: `0px`,
},
}));
const Component: React.FC<ComponentProps> = memo(props => {
const styles = useStyles();
const { students, push, remove, setFieldValue } = props;
function deleteStudent(index: number) {
if (!window.confirm('¿Desea borrar este estudiante?')) {
return;
}
remove(index);
}
const addStudent = () => push(emptyStudent());
const selectAllOptions = (evt: React.ChangeEvent<HTMLInputElement>) => {
students.forEach(student => (student.gender = evt.target.value as Gender));
console.log(students);
setFieldValue('students', students);
};
return (
<>
Cambiar el género a todos los estudiantes:{' '}
<Select
name="select_all"
options={selectOptions}
onChange={selectAllOptions}
/>{' '}
<br />
<Paper style={{ width: '100%' }}>
<Table style={{ width: '100%', padding: 'root' }}>
<TableHead>
<TableRow>
<TableCell>#</TableCell>
<TableCell>Nombre</TableCell>
<TableCell>Apellido</TableCell>
<TableCell>Género</TableCell>
<TableCell>Email</TableCell>
<TableCell>Cumpleaños</TableCell>
<TableCell>Alergias</TableCell>
<TableCell>Enfermedades</TableCell>
<TableCell>Acción</TableCell>
</TableRow>
</TableHead>
<TableBody>
{students.map((student, index) => (
<BulkAddTableRow
key={`${student.name}-${index}`}
student={student}
deleteStudent={deleteStudent}
index={index}
/>
))}
<TableRow>
<TableCell colSpan={8}></TableCell>
<TableCell>
<Button onClick={addStudent}>+</Button>
</TableCell>
</TableRow>
</TableBody>
</Table>
</Paper>
</>
);
});
export const StudentBulkTableView: React.FC = props => {
const { setFieldValue } = useFormikContext<BulkAddStudentValues>();
return (
<FieldArray name="students">
{({ remove, push, form }: FieldArrayRenderProps) => {
const students = getIn(form.values, 'students') as Student[];
return (
<Component
setFieldValue={setFieldValue}
remove={remove}
push={push}
students={students}
/>
);
}}
</FieldArray>
);
};
export default StudentBulkTableView;
P.S:我排除了 <FormTextArea />
组件,因为它与 <FormText />
组件完全相同。
根据您描述的行为,听起来您对每一行使用的 key
可能有问题。
<BulkAddTableRow
key={`${student.name}-${index}`}
看起来 student.name
是一个对象,这意味着您的 key
将是 "[object Object]-0"
、"[object Object]-1"
等。基于索引的键在以下情况下会导致问题删除行,因为 React 不知道该索引的值已更改。
这是一篇描述问题的文章:https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
你可以console.log每一行的key
,如果它们是[object-Object]
加上索引,你可以这样做:
<BulkAddTableRow
key={`${student.name.firstName}-${student.name.lastName}`}
这个答案适用于@PrateekPareek,我将分享如何使用 Formik 2 实现高性能的 onBlur。
这将使用 Hooks,这与我上面发布的有很大不同。
我也在用 Material UI.
这里的技巧是使用内部状态来处理 Material UI TextField 并使用 onBlur 将更改传播到 Formik。不幸的是,这有一个缺点,即无法检测到密码等预填字段。
为了缓解这种情况,有一种替代方法使用去抖动函数和 useEffect 在用户键入时传播 onChange 事件,而不是使用 onBlur(我没有在这里分享,因为我还没有创建它).
我通常倾向于将我的表单文本字段包装在它们自己的组件中,所以只需将它们放入正在使用的组件中即可。
有一个“disablePerformance”道具,基本上可以让 Formik 处理整个组件。
import React, { memo, useState } from 'react';
import {
TextField,
TextFieldProps,
} from '../../../../../stories/Fields/TextField';
import { useField } from 'formik';
export type FormTextProps = Omit<TextFieldProps, 'name'> & {
name: string;
disablePerformance?: boolean;
};
export const FormText: React.FC<FormTextProps> = memo((props) => {
const [field, meta] = useField(props.name);
const error = !!meta.error && meta.touched;
/**
* For performance reasons (possible due to CSS in JS issues), heavy views
* affect re-renders (Formik changes state in every re-render), bringing keyboard
* input to its knees. To control this, we create a setState that handles the field's inner
* (otherwise you wouldn't be able to type) and then propagate the change to Formik onBlur and
* onFocus.
*/
const [fieldValue, setFieldValue] = useState<string | number>(field.value);
const { disablePerformance, ...otherProps } = props;
const onChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
setFieldValue(evt.target.value);
};
const onBlur = (evt: React.FocusEvent<HTMLInputElement>) => {
field.onChange({
target: {
name: props.name,
value: evt.target.value || '',
},
});
};
// Will set depending on the performance props
const performanceProps = disablePerformance
? field
: {
...field,
value: fieldValue,
onChange,
onBlur,
onFocus: onBlur,
};
return (
<>
<TextField
{...otherProps}
error={error}
helperText={meta.touched && meta.error}
{...performanceProps}
/>
</>
);
});
export default FormText;
仅供参考,这是 TextField,它是 Material-UI 的包装器,因此我可以设置一些默认值。
import React, { memo } from 'react';
import {
TextField as MuiTextField,
TextFieldProps as MuiTextFieldProps,
} from '@material-ui/core';
export type TextFieldProps = MuiTextFieldProps & {};
export const TextField: React.FC<TextFieldProps> = memo((props) => {
// muiProps => The props for MaterialUI
const { ...muiProps } = props;
return (
<>
<MuiTextField
fullWidth={true}
InputLabelProps={{
shrink: true,
style: { textTransform: 'uppercase' },
}}
{...muiProps}
>
{props.children}
</MuiTextField>
</>
);
});
export default TextField;
我目前正在呈现一个 editable table,它允许用户一次批量编辑多个用户的信息(见图)。我正在使用 Material-UI 的 <TextField/>
和 Formik 来处理表单的提交和状态。
我正在努力:
- 保持
<TextField />
的值和 Formik 的状态同步 - 每当我删除一行时(单击 x 时),以反映整个变化 table。
此 table 通常包含大约 266 个输入字段。使用 onChange
事件会带来严重的性能问题。因此,我不得不应用多个组件包装和记忆,以防止每次单个输入更改时重新呈现所有输入字段。
我已经成功地完成了这项工作(几乎以一种良好的性能方式),除了每当我删除一行时。旧值似乎仍然存在,而 Formik 的值确实发生了变化。
问题似乎在于 <TextField />
的 defaultValue
和 value
属性如何工作。
value
属性 似乎创建了一个受控组件,并且会一对一地反映您在其中传递的任何值。我试过将 Formik 的 field.value
直接设置到字段中。不幸的是,该值不会更新该字段,因为我目前正在使用 onBlur 事件来执行此操作(并且永远不会显示更改)。如果我要使用 onChange,一切都会正常,除了性能会很差,因为它会更新所有字段。
另一方面,defaultValue
使组件不受控制。尽管如此,我还是可以编辑该值,甚至可以更新 Formik 的状态 onBlur
!。 有一个问题,但是...每当我删除一行时,<TextField/>
中的值不会更新(但是Formik 是否反映了变化)。
似乎 <TextField />
组件内部正在进行一些缓存,因为我已经尝试记录该字段的值,这是我当前传递给 defaultValue
的值,它 正在显示 变化。
我也试过:
- 修补
defaultValue
和value
- 设置一个
useState
挂钩作为 Formik 值和组件值之间的中间人 - 正在删除备忘。
- 手动实现记忆比较。
其中 none 似乎有效...在这种情况下我该怎么办?
作为参考,这是我使用的代码:
这是我当前使用的文本字段:
FormText
import React, { memo } from 'react';
import { useField } from 'formik';
import TextField from '@material-ui/core/TextField';
import { TextProps } from '../../../Fields/TextField/textfield-definitions';
type ComponentProps = TextProps & {
useBlur?: boolean;
errorMessage: string | undefined;
};
export const Component: React.FC<ComponentProps> = memo(props => {
const {
className,
name,
label,
placeholder,
required,
useBlur,
error,
errorMessage,
onChange,
onBlur,
value,
} = props;
// We wrap it so we don't block the heap stack!
// Improves performance considerably
// https://medium.com/trabe/react-syntheticevent-reuse-889cd52981b6
const fireBlur = (e: any) => {
// React removes
e.persist();
window.setTimeout(() => {
if (onBlur) {
onBlur(e);
}
}, 0);
};
const setInnerState = (e: React.ChangeEvent<HTMLInputElement>) => {};
const fireChange = (e: React.ChangeEvent<HTMLInputElement>) => {
e.persist();
setInnerState(e);
window.setTimeout(() => {
if (onChange) {
onChange(e);
}
}, 0);
};
return (
<TextField
className={className}
name={name}
label={label}
type={props.type}
placeholder={placeholder}
defaultValue={value}
variant="outlined"
required={required}
error={error}
helperText={<span>{error ? errorMessage : ''}</span>}
onChange={useBlur ? undefined : fireChange}
onBlur={useBlur ? fireBlur : undefined}
/>
);
});
export const SchonText: React.FC<TextProps> = props => {
const [field, meta] = useField(props.name);
const hasError = !!meta.error && !!meta.touched;
return (
<Component
value={field.value}
{...props}
error={hasError}
errorMessage={meta.error}
onChange={field.onChange}
onBlur={field.onChange}
/>
);
};
export default SchonText;
以下是使用它的组件:
TableRow
import React, { memo } from 'react';
import { TableRow, TableCell, makeStyles } from '@material-ui/core';
import { Close } from '@material-ui/icons';
import {
FormText,
FormSelect,
FormTextArea,
Button,
} from '../../../../../../components';
import { Student, Gender } from '../../../../../../graphql/types';
import { SelectOption } from '../../../../../../components/Fields/Select/select-definitions';
type BulkAddTableRowProps = {
student: Student;
index: number;
deleteStudent: (index: number) => void;
};
const useStyles = makeStyles(theme => ({
root: {
padding: `0px`,
},
}));
const selectOptions: SelectOption[] = [
{
label: 'M',
value: Gender.Male,
},
{
label: 'F',
value: Gender.Female,
},
];
const Component: React.FC<BulkAddTableRowProps> = props => {
const styles = useStyles();
const { student, index } = props;
const deleteStudent = () => props.deleteStudent(index);
return (
<TableRow className={styles.root} hover={true}>
<TableCell>{index + 1}</TableCell>
<TableCell className={styles.root}>
<FormText
name={`students[${index}].name.firstName`}
value={student.name.firstName}
useBlur={true}
/>
</TableCell>
<TableCell>
<FormText
name={`students[${index}].name.lastName`}
value={student.name.lastName}
useBlur={true}
/>
</TableCell>
<TableCell>
<FormSelect
name={`students[${index}].gender`}
value={student.gender}
options={selectOptions}
/>
</TableCell>
<TableCell>
<FormText
type="email"
name={`students[${index}].email`}
value={student.email}
useBlur={true}
/>
</TableCell>
<TableCell>
<FormText
type="date"
name={`students[${index}].birthDate`}
value={student.birthDate}
useBlur={true}
/>
</TableCell>
<TableCell>
<FormTextArea
name={`students[${index}].allergies`}
value={student.allergies}
useBlur={true}
/>
</TableCell>
<TableCell>
<FormTextArea
name={`students[${index}].diseases`}
value={student.diseases}
useBlur={true}
/>
</TableCell>
<TableCell>
<Button onClick={deleteStudent}>
<Close />
</Button>
</TableCell>
</TableRow>
);
};
function shouldRemainTheSame(
prevProps: BulkAddTableRowProps,
newProps: BulkAddTableRowProps,
): boolean {
const prevStudent = prevProps.student;
const newStudent = newProps.student;
const isNameTheSame = Object.keys(prevStudent.name).every(key => {
return prevStudent.name[key] === newStudent.name[key];
});
const isStudentTheSame = Object.keys(prevStudent)
.filter(x => x !== 'name')
.every(key => prevStudent[key] === newStudent[key]);
return (
isNameTheSame && isStudentTheSame && prevProps.index === newProps.index
);
}
export const BulkAddTableRow = memo(Component, shouldRemainTheSame);
export default BulkAddTableRow;
StudentBulkTableView
import React, { memo } from 'react';
import {
FieldArray,
FieldArrayRenderProps,
getIn,
useFormikContext,
} from 'formik';
import { Student, Gender } from '../../../../graphql/types/index';
import {
Paper,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
makeStyles,
} from '@material-ui/core';
import { Button, Select } from '../../../../components';
import { SelectOption } from '../../../../components/Fields/Select/select-definitions';
import { emptyStudent, BulkAddStudentValues } from '../shared';
import BulkAddTableRow from './components/TableRow/index';
type ComponentProps = {
push: (obj: any) => void;
remove: (index: number) => undefined;
students: Student[];
setFieldValue: (
field: 'students',
value: any,
shouldValidate?: boolean | undefined,
) => void;
};
const selectOptions: SelectOption[] = [
{
label: 'M',
value: Gender.Male,
},
{
label: 'F',
value: Gender.Female,
},
];
const useStyles = makeStyles(theme => ({
root: {
padding: `0px`,
},
}));
const Component: React.FC<ComponentProps> = memo(props => {
const styles = useStyles();
const { students, push, remove, setFieldValue } = props;
function deleteStudent(index: number) {
if (!window.confirm('¿Desea borrar este estudiante?')) {
return;
}
remove(index);
}
const addStudent = () => push(emptyStudent());
const selectAllOptions = (evt: React.ChangeEvent<HTMLInputElement>) => {
students.forEach(student => (student.gender = evt.target.value as Gender));
console.log(students);
setFieldValue('students', students);
};
return (
<>
Cambiar el género a todos los estudiantes:{' '}
<Select
name="select_all"
options={selectOptions}
onChange={selectAllOptions}
/>{' '}
<br />
<Paper style={{ width: '100%' }}>
<Table style={{ width: '100%', padding: 'root' }}>
<TableHead>
<TableRow>
<TableCell>#</TableCell>
<TableCell>Nombre</TableCell>
<TableCell>Apellido</TableCell>
<TableCell>Género</TableCell>
<TableCell>Email</TableCell>
<TableCell>Cumpleaños</TableCell>
<TableCell>Alergias</TableCell>
<TableCell>Enfermedades</TableCell>
<TableCell>Acción</TableCell>
</TableRow>
</TableHead>
<TableBody>
{students.map((student, index) => (
<BulkAddTableRow
key={`${student.name}-${index}`}
student={student}
deleteStudent={deleteStudent}
index={index}
/>
))}
<TableRow>
<TableCell colSpan={8}></TableCell>
<TableCell>
<Button onClick={addStudent}>+</Button>
</TableCell>
</TableRow>
</TableBody>
</Table>
</Paper>
</>
);
});
export const StudentBulkTableView: React.FC = props => {
const { setFieldValue } = useFormikContext<BulkAddStudentValues>();
return (
<FieldArray name="students">
{({ remove, push, form }: FieldArrayRenderProps) => {
const students = getIn(form.values, 'students') as Student[];
return (
<Component
setFieldValue={setFieldValue}
remove={remove}
push={push}
students={students}
/>
);
}}
</FieldArray>
);
};
export default StudentBulkTableView;
P.S:我排除了 <FormTextArea />
组件,因为它与 <FormText />
组件完全相同。
根据您描述的行为,听起来您对每一行使用的 key
可能有问题。
<BulkAddTableRow
key={`${student.name}-${index}`}
看起来 student.name
是一个对象,这意味着您的 key
将是 "[object Object]-0"
、"[object Object]-1"
等。基于索引的键在以下情况下会导致问题删除行,因为 React 不知道该索引的值已更改。
这是一篇描述问题的文章:https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
你可以console.log每一行的key
,如果它们是[object-Object]
加上索引,你可以这样做:
<BulkAddTableRow
key={`${student.name.firstName}-${student.name.lastName}`}
这个答案适用于@PrateekPareek,我将分享如何使用 Formik 2 实现高性能的 onBlur。
这将使用 Hooks,这与我上面发布的有很大不同。
我也在用 Material UI.
这里的技巧是使用内部状态来处理 Material UI TextField 并使用 onBlur 将更改传播到 Formik。不幸的是,这有一个缺点,即无法检测到密码等预填字段。
为了缓解这种情况,有一种替代方法使用去抖动函数和 useEffect 在用户键入时传播 onChange 事件,而不是使用 onBlur(我没有在这里分享,因为我还没有创建它).
我通常倾向于将我的表单文本字段包装在它们自己的组件中,所以只需将它们放入正在使用的组件中即可。
有一个“disablePerformance”道具,基本上可以让 Formik 处理整个组件。
import React, { memo, useState } from 'react';
import {
TextField,
TextFieldProps,
} from '../../../../../stories/Fields/TextField';
import { useField } from 'formik';
export type FormTextProps = Omit<TextFieldProps, 'name'> & {
name: string;
disablePerformance?: boolean;
};
export const FormText: React.FC<FormTextProps> = memo((props) => {
const [field, meta] = useField(props.name);
const error = !!meta.error && meta.touched;
/**
* For performance reasons (possible due to CSS in JS issues), heavy views
* affect re-renders (Formik changes state in every re-render), bringing keyboard
* input to its knees. To control this, we create a setState that handles the field's inner
* (otherwise you wouldn't be able to type) and then propagate the change to Formik onBlur and
* onFocus.
*/
const [fieldValue, setFieldValue] = useState<string | number>(field.value);
const { disablePerformance, ...otherProps } = props;
const onChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
setFieldValue(evt.target.value);
};
const onBlur = (evt: React.FocusEvent<HTMLInputElement>) => {
field.onChange({
target: {
name: props.name,
value: evt.target.value || '',
},
});
};
// Will set depending on the performance props
const performanceProps = disablePerformance
? field
: {
...field,
value: fieldValue,
onChange,
onBlur,
onFocus: onBlur,
};
return (
<>
<TextField
{...otherProps}
error={error}
helperText={meta.touched && meta.error}
{...performanceProps}
/>
</>
);
});
export default FormText;
仅供参考,这是 TextField,它是 Material-UI 的包装器,因此我可以设置一些默认值。
import React, { memo } from 'react';
import {
TextField as MuiTextField,
TextFieldProps as MuiTextFieldProps,
} from '@material-ui/core';
export type TextFieldProps = MuiTextFieldProps & {};
export const TextField: React.FC<TextFieldProps> = memo((props) => {
// muiProps => The props for MaterialUI
const { ...muiProps } = props;
return (
<>
<MuiTextField
fullWidth={true}
InputLabelProps={{
shrink: true,
style: { textTransform: 'uppercase' },
}}
{...muiProps}
>
{props.children}
</MuiTextField>
</>
);
});
export default TextField;