【问题标题】:How to render an editable table with formik 2 and react-table 7?如何使用 formik 2 和 react-table 7 渲染可编辑的表格?
【发布时间】:2020-04-23 00:37:18
【问题描述】:

我有这种情况,我从服务器加载表单的数据(假设是具有用户朋友列表的用户实体)。

该表单具有可编辑名称的朋友列表,该列表呈现为带有 react-table 7 的表格。 我面临的问题是,每当我尝试编辑此列表中朋友的姓名时,我只能输入一个字符,然后输入失去焦点。我再次单击输入,键入 1 个字符,它再次失去焦点。

我创建了一个代码框来说明问题:https://codesandbox.io/s/formik-react-table-hr1l4

我理解为什么会发生这种情况 - 每次我输入时表格都会重新呈现,因为 formik 状态发生了变化 - 但我不确定如何防止这种情况发生。我useMemo-ed 和useCallback-ed 所有我能想到的(还有React.memo-ed 组件,希望它能防止问题发生),但到目前为止还没有运气。

如果我删除Friends 中的useEffect,它确实有效,但是,这将使表在超时到期后不会更新(因此它不会在1s 后显示2 个朋友)。 非常感谢任何帮助...我整天都被这个问题困扰。

【问题讨论】:

    标签: reactjs react-hooks formik react-table


    【解决方案1】:

    哇,你真的很享受使用 React 附带的所有不同钩子的乐趣 ;-) 我看了你的代码和框大约 15 分钟。我的观点是,对于这样一个简单的任务,它的设计太过分了。没有恶意。我会做什么:

    • 尝试退后一步,通过重构您的 index.js 并按照 Formik 主页上的预期使用 FieldArray(为每位朋友提供一个渲染)。
    • 下一步,您可以围绕它构建一个简单的表格
    • 然后您可以尝试使用输入字段使不同的字段可编辑
    • 如果你真的需要它,你可以添加 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);
    

    现在一切都是一个组件。如果您愿意,您现在可以将它重构为不同的组件,或者检查您可以应用什么样的钩子;-) 从简单开始并使其工作。然后你就可以继续剩下的了。

    更新

    当您像这样更新 Friends 组件时:

    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 块,该表直接与formikSlice 一起使用。我猜问题在于,当您更改输入时,Formik 值已更新,并且触发了 useEffect 块以更新 Friends 组件的内部状态,从而导致表格重新呈现。

    【讨论】:

    • 非常感谢您花时间查看和回答!很好的提醒,我们有时需要退后一步,从远处看!代码是过度设计的,因为它是从一个更大的项目中提取的,这就是 react-table 要求的原因。最后,您的代码和我的代码之间的区别在于您的代码是手动循环 values.friends 所以它不介意它是否发生了变异。我的依赖于 react-table,当它发生变化时会重新渲染。
    • 嗨,丹,我为我的回答添加了一个可能的解决方案。你能检查一下它是否也适合你吗?
    • 哇,它有效!我发誓我一开始就没有useEffect,它没有用,但也许还有其他东西在起作用……我渴望在我的项目中测试它,并会在本周末回复你。
    • 很高兴听到:)
    • 在项目中测试,可以!谢谢你拯救我的理智,我欠你一个:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-02
    • 1970-01-01
    • 2020-11-11
    相关资源
    最近更新 更多