javascript - 使用 Formik (React) 更新 Material-UI TextField onBlur
问题描述
我目前正在渲染一个可编辑的表格,让用户可以一次批量编辑多个用户的信息(参见图片)。我正在使用 Material-UI<TextField/>
和 Formik 来处理表单的提交和状态。
我试图:
- 保持
<TextField />
' 值和 Formik 状态同步 - 每当我删除一行时(单击 x 时),以反映整个表中的更改。
该表通常包括大约 266 个输入字段。使用onChange
事件会带来严重的性能问题。因此,我必须应用多个组件包装和记忆化,以防止每次单个输入发生更改时所有输入字段都重新呈现。
我已经成功地完成了这项工作(几乎以一种良好的性能方式),除非我删除一行。旧的价值似乎仍然存在,而 Formik 的价值确实发生了变化。
问题似乎在于工作的方式defaultValue
和value
属性<TextField />
。
该value
属性似乎创建了一个受控组件,并且将一对一地反映您在其中传递的任何值。我已经尝试将 Formik'sfield.value
直接设置到该字段中。不幸的是,该值不会更新该字段,因为我目前正在使用 onBlur 事件来执行此操作(并且永远不会显示更改)。如果我要使用 onChange,一切都会正常工作,除了性能将是垃圾,因为它会更新所有字段。
另一方面,这defaultValue
使组件不受控制。尽管如此,我还是可以编辑值,甚至更新 Formik 的状态onBlur
!但是有一个问题......每当我删除一行时,里面的值<TextField/>
不会更新(但 Formik确实反映了变化)。
似乎组件内部正在进行一些缓存<TextField />
,因为我尝试记录该字段的值,这是我当前传递给的值,defaultValue
它正在显示更改。
我也试过:
- 修补
defaultValue
和value
- 设置一个
useState
钩子作为 Formik 的值和组件的值之间的中间人 - 删除备忘录。
- 手动实现了记忆比较。
而且它们似乎都不起作用......在这种情况下我该怎么办?
作为参考,这是我正在使用的代码:
这是我目前正在使用的文本字段:
表单文本
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;
PS:我已经排除了<FormTextArea />
组件,因为它与组件完全相同<FormText />
。
解决方案
根据您描述的行为,听起来key
您为每一行使用的可能存在问题。
<BulkAddTableRow
key={`${student.name}-${index}`}
它看起来像是student.name
一个对象,这意味着您key
的 s 将是"[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}`}
推荐阅读
- javascript - 使用 Jquery 显示 Flask JSON 数据
- react-redux - 使用承诺但未按顺序分派的操作
- c# - 如何捕获线程中发生的 Asp.net 核心中的异常
- php - 如何使用重定向禁用目录文件列表
- python - 每第 n 个字符拆分字符串列表
- css - 使用 CSS 和 Bootstrap 4 获取选项框的格式
- pdfsharp - 在 Migradoc 最后一页添加页脚
- appium - 如何在 iphone 上检查 IOS 应用程序中的元素?
- c# - 身份服务器 4/nativescript 挂起
- visual-studio-code - “未解决的导入”-错误:在 VSCode 中包括代码完成和 pylint 的路径