如何使用 formik 2 和 react-table 7 渲染 editable table?
How to render an editable table with formik 2 and react-table 7?
我有这样一个场景,我从服务器加载表单的数据(假设是一个用户实体和用户的朋友列表)。
该表单包含带有 editable 名字的朋友列表,呈现为带有 react-table 7 的 table。
我面临的问题是,每当我尝试编辑此列表中朋友的姓名时,我只能键入一个字符,然后输入失去焦点。我再次单击输入,键入 1 个字符,它再次失去焦点。
我创建了一个codesandbox来说明这个问题:https://codesandbox.io/s/formik-react-table-hr1l4
我理解为什么会发生这种情况 - table 每次我键入时都会重新呈现,因为 formik 状态发生变化 - 但我不确定如何防止这种情况发生。我 useMemo
-ed 和 useCallback
-ed 所有我能想到的(也 React.memo
-ed 组件希望它能防止问题),但到目前为止没有运气。
如果我删除 Friends
中的 useEffect
它确实有效,但是,这将使 table 在超时到期后不更新(因此它不会显示1 秒后有 2 个朋友)。
非常感谢任何帮助...我整天都被这个问题困住了。
哇,你真的很喜欢使用 React 附带的所有不同的钩子 ;-) 我现在看了你的 codesandbox 大约 15 分钟。我的观点是,对于这样一个简单的任务来说,它的设计有点过头了。没有恶意。我会做什么:
- 尝试退后一步,通过重构您的 index.js 并按照 Formik 主页上的预期使用
FieldArray
(每个朋友一个渲染)。
- 作为下一步,您可以围绕它构建一个简单的 table
- 那么你可以尝试用输入字段
制作不同的字段editable
- 如果你真的需要它,你可以添加
react-table
库,但我认为没有它应该很容易实现它
这里有一些代码可以向您展示我的意思:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { Formik, Form, FieldArray, Field } from "formik";
import Input from "./Input";
import "./styles.css";
const initialFormData = undefined;
function App() {
const [formData, setFormData] = useState(initialFormData);
useEffect(() => {
// this is replacement for a network call that would load the data from a server
setTimeout(() => {
setFormData({
id: 1,
firstName: "First Name 1",
friends: [
{ id: 2, firstName: "First Name 2", lastName: "Last Name 2" },
{ id: 3, firstName: "First Name 3", lastName: "Last Name 3" }
]
});
}, 1000);
// Missing dependency array here
}, []);
return (
<div className="app">
{formData && (
<Formik initialValues={formData} enableReinitialize>
{({ values }) => (
<Form>
<Input name="name" label="Name: " />
<FieldArray name="friends">
{arrayHelpers => (
<div>
<button
onClick={() =>
arrayHelpers.push({
id: Math.floor(Math.random() * 100) / 10,
firstName: "",
lastName: ""
})
}
>
add
</button>
<table>
<thead>
<tr>
<th>ID</th>
<th>FirstName</th>
<th>LastName</th>
<th />
</tr>
</thead>
<tbody>
{values.friends && values.friends.length > 0 ? (
values.friends.map((friend, index) => (
<tr key={index}>
<td>{friend.id}</td>
<td>
<Input name={`friends[${index}].firstName`} />
</td>
<td>
<Input name={`friends[${index}].lastName`} />
</td>
<td>
<button
onClick={() => arrayHelpers.remove(index)}
>
remove
</button>
</td>
</tr>
))
) : (
<tr>
<td>no friends :(</td>
</tr>
)}
</tbody>
</table>
</div>
)}
</FieldArray>
</Form>
)}
</Formik>
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
现在一切都是一个组件。如果您愿意,您现在可以将它重构为不同的组件,或者检查您可以应用哪种挂钩 ;-) 从简单开始,让它发挥作用。然后你就可以继续剩下的了。
更新:
当您像这样更新好友组件时:
import React, { useCallback, useMemo } from "react";
import { useFormikContext, getIn } from "formik";
import Table from "./Table";
import Input from "./Input";
const EMPTY_ARR = [];
function Friends({ name, handleAdd, handleRemove }) {
const { values } = useFormikContext();
// from all the form values we only need the "friends" part.
// we use getIn and not values[name] for the case when name is a path like `social.facebook`
const formikSlice = getIn(values, name) || EMPTY_ARR;
const onAdd = useCallback(() => {
const item = {
id: Math.floor(Math.random() * 100) / 10,
firstName: "",
lastName: ""
};
handleAdd(item);
}, [handleAdd]);
const onRemove = useCallback(
index => {
handleRemove(index);
},
[handleRemove]
);
const columns = useMemo(
() => [
{
Header: "Id",
accessor: "id"
},
{
Header: "First Name",
id: "firstName",
Cell: ({ row: { index } }) => (
<Input name={`${name}[${index}].firstName`} />
)
},
{
Header: "Last Name",
id: "lastName",
Cell: ({ row: { index } }) => (
<Input name={`${name}[${index}].lastName`} />
)
},
{
Header: "Actions",
id: "actions",
Cell: ({ row: { index } }) => (
<button type="button" onClick={() => onRemove(index)}>
delete
</button>
)
}
],
[name, onRemove]
);
return (
<div className="field">
<div>
Friends:{" "}
<button type="button" onClick={onAdd}>
add
</button>
</div>
<Table data={formikSlice} columns={columns} rowKey="id" />
</div>
);
}
export default React.memo(Friends);
好像没有散焦了。你也可以检查一下吗?我删除了 useEffect 块,table 直接与 formikSlice
一起工作。我想问题在于,当您更改输入时,Formik 值已更新并且 useEffect 块被触发以更新 Friends 组件的内部状态,导致 table 重新呈现。
我有这样一个场景,我从服务器加载表单的数据(假设是一个用户实体和用户的朋友列表)。
该表单包含带有 editable 名字的朋友列表,呈现为带有 react-table 7 的 table。 我面临的问题是,每当我尝试编辑此列表中朋友的姓名时,我只能键入一个字符,然后输入失去焦点。我再次单击输入,键入 1 个字符,它再次失去焦点。
我创建了一个codesandbox来说明这个问题:https://codesandbox.io/s/formik-react-table-hr1l4
我理解为什么会发生这种情况 - table 每次我键入时都会重新呈现,因为 formik 状态发生变化 - 但我不确定如何防止这种情况发生。我 useMemo
-ed 和 useCallback
-ed 所有我能想到的(也 React.memo
-ed 组件希望它能防止问题),但到目前为止没有运气。
如果我删除 Friends
中的 useEffect
它确实有效,但是,这将使 table 在超时到期后不更新(因此它不会显示1 秒后有 2 个朋友)。
非常感谢任何帮助...我整天都被这个问题困住了。
哇,你真的很喜欢使用 React 附带的所有不同的钩子 ;-) 我现在看了你的 codesandbox 大约 15 分钟。我的观点是,对于这样一个简单的任务来说,它的设计有点过头了。没有恶意。我会做什么:
- 尝试退后一步,通过重构您的 index.js 并按照 Formik 主页上的预期使用
FieldArray
(每个朋友一个渲染)。 - 作为下一步,您可以围绕它构建一个简单的 table
- 那么你可以尝试用输入字段 制作不同的字段editable
- 如果你真的需要它,你可以添加
react-table
库,但我认为没有它应该很容易实现它
这里有一些代码可以向您展示我的意思:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { Formik, Form, FieldArray, Field } from "formik";
import Input from "./Input";
import "./styles.css";
const initialFormData = undefined;
function App() {
const [formData, setFormData] = useState(initialFormData);
useEffect(() => {
// this is replacement for a network call that would load the data from a server
setTimeout(() => {
setFormData({
id: 1,
firstName: "First Name 1",
friends: [
{ id: 2, firstName: "First Name 2", lastName: "Last Name 2" },
{ id: 3, firstName: "First Name 3", lastName: "Last Name 3" }
]
});
}, 1000);
// Missing dependency array here
}, []);
return (
<div className="app">
{formData && (
<Formik initialValues={formData} enableReinitialize>
{({ values }) => (
<Form>
<Input name="name" label="Name: " />
<FieldArray name="friends">
{arrayHelpers => (
<div>
<button
onClick={() =>
arrayHelpers.push({
id: Math.floor(Math.random() * 100) / 10,
firstName: "",
lastName: ""
})
}
>
add
</button>
<table>
<thead>
<tr>
<th>ID</th>
<th>FirstName</th>
<th>LastName</th>
<th />
</tr>
</thead>
<tbody>
{values.friends && values.friends.length > 0 ? (
values.friends.map((friend, index) => (
<tr key={index}>
<td>{friend.id}</td>
<td>
<Input name={`friends[${index}].firstName`} />
</td>
<td>
<Input name={`friends[${index}].lastName`} />
</td>
<td>
<button
onClick={() => arrayHelpers.remove(index)}
>
remove
</button>
</td>
</tr>
))
) : (
<tr>
<td>no friends :(</td>
</tr>
)}
</tbody>
</table>
</div>
)}
</FieldArray>
</Form>
)}
</Formik>
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
现在一切都是一个组件。如果您愿意,您现在可以将它重构为不同的组件,或者检查您可以应用哪种挂钩 ;-) 从简单开始,让它发挥作用。然后你就可以继续剩下的了。
更新:
当您像这样更新好友组件时:
import React, { useCallback, useMemo } from "react";
import { useFormikContext, getIn } from "formik";
import Table from "./Table";
import Input from "./Input";
const EMPTY_ARR = [];
function Friends({ name, handleAdd, handleRemove }) {
const { values } = useFormikContext();
// from all the form values we only need the "friends" part.
// we use getIn and not values[name] for the case when name is a path like `social.facebook`
const formikSlice = getIn(values, name) || EMPTY_ARR;
const onAdd = useCallback(() => {
const item = {
id: Math.floor(Math.random() * 100) / 10,
firstName: "",
lastName: ""
};
handleAdd(item);
}, [handleAdd]);
const onRemove = useCallback(
index => {
handleRemove(index);
},
[handleRemove]
);
const columns = useMemo(
() => [
{
Header: "Id",
accessor: "id"
},
{
Header: "First Name",
id: "firstName",
Cell: ({ row: { index } }) => (
<Input name={`${name}[${index}].firstName`} />
)
},
{
Header: "Last Name",
id: "lastName",
Cell: ({ row: { index } }) => (
<Input name={`${name}[${index}].lastName`} />
)
},
{
Header: "Actions",
id: "actions",
Cell: ({ row: { index } }) => (
<button type="button" onClick={() => onRemove(index)}>
delete
</button>
)
}
],
[name, onRemove]
);
return (
<div className="field">
<div>
Friends:{" "}
<button type="button" onClick={onAdd}>
add
</button>
</div>
<Table data={formikSlice} columns={columns} rowKey="id" />
</div>
);
}
export default React.memo(Friends);
好像没有散焦了。你也可以检查一下吗?我删除了 useEffect 块,table 直接与 formikSlice
一起工作。我想问题在于,当您更改输入时,Formik 值已更新并且 useEffect 块被触发以更新 Friends 组件的内部状态,导致 table 重新呈现。