如何使用 formik 2 和 react-table 7 呈现可编辑表格?

Dan*_*gea 5 reactjs react-table formik react-hooks

我有一个场景,我从服务器加载表单数据(假设一个用户实体与用户的朋友列表)。

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

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

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

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

Kla*_*aus 8

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

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

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

更新

当您像这样更新 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);
Run Code Online (Sandbox Code Playgroud)

它似乎不再散焦了。你也可以检查一下吗?我删除了 useEffect 块,该表直接与formikSlice. 我想问题在于,当您更改 Formik 值更新的输入时,会触发 useEffect 块以更新 Friends 组件的内部状态,从而导致表重新呈现。