Formik 重新呈现反应状态变化

Formik re-renders on react state change

我在 formik 字段中使用自定义组件,需要在函数内部更改文本时调用一个函数(以便获得与输入相关的所有匹配提示)

 <Field
            id="assignees"
            name="assignees"
            component={({
              field, // { name, value, onChange, onBlur }
              form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
              ...props
            }) => {
              return (
                    <input
                      key="assignees"
                      className="input-fields"
                      placeholder="Type names of assignees to start getting suggestions"
                      onChange={(e) => {
                        form.setFieldValue(field.name, e.target.value);
                        getMatching(field.value);
                      }}
                      {...props}
                      {...form}
                      //   {...field}
                    />
    );
}}
/>

上面代码调用的匹配函数如下。它更新状态,然后该状态显示在 formik 字段中的跨度内。

const getMatching = (keyword) => {
    for (const item of users) {
      if (keyword !== "" && item.name.startsWith(keyword)) {
        setMatching(item.name);
        return;
      } else {
        setMatching(undefined);
      }
    }
  };

预期行为
Formik 输入不会失去焦点并保留我在状态更改或重新渲染时添加的所有输入文本

实际行为
每当 getMatching 函数

设置状态时,输入组件失去焦点并且字段被重置

编辑-代码沙盒LINK
https://codesandbox.io/s/wizardly-merkle-w191l

几件事:

  • 无需在状态中存储matching,可以通过查看受让人文本框值和用户列表来导出
  • 受让人文本框(我称之为 searchAssignee)和选定受让人列表(assignees)本质上是两个不同的输入,因此必须是单独的 Formik 输入
  • 将以上两个输入分开解决焦点问题

formik 的整个想法是它消除了在状态中存储输入值的需要。我删除了不必要的状态并分离了上面的输入。

Working Example

const getMatching = (users, keyword) => {
  if (!keyword) return null;
  const match = users.find(({ name }) => name.startsWith(keyword));
  if (!match) return null;
  return match.name;
};

const FormContainer = ({ users }) => {
  return (
    <Formik
      initialValues={{
        summary: "",
        description: "",
        searchAssignee: "",
        assignees: []
      }}
      onSubmit={async (values) => {
        console.log(values);
      }}
    >
      <Form>
        <Row>
          <Col>
            <div className="textcolor">Summary</div>
            <Field
              id="summary"
              name="summary"
              placeholder="Add a short summary for your task"
              component={({
                field, // { name, value, onChange, onBlur }
                form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
                ...props
              }) => {
                return (
                  <input
                    className="input-fields"
                    {...props}
                    // {...form}
                    {...field}
                  />
                );
              }}
            />
          </Col>
        </Row>
        <Row>
          <Col>
            <div className="textcolor">Description</div>
            <Field
              id="description"
              name="description"
              placeholder="Description"
              component={({
                field, // { name, value, onChange, onBlur }
                form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
                ...props
              }) => {
                return (
                  <ReactQuill
                    theme="snow"
                    value={field.value}
                    onChange={field.onChange(field.name)}
                    style={{ minHeight: "5rem" }}
                    {...field}
                    {...props}
                  />
                );
              }}
            />
          </Col>
        </Row>
        <Row>
          <Col>
            <div className="textcolor">Assignees</div>
            <Col className="assignee-wrapper d-flex">
              <div className="d-flex">
                <Field
                  id="assignees"
                  name="assignees"
                  component={({
                    field, // { name, value, onChange, onBlur }
                    form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
                    ...props
                  }) => (
                    <>
                      {field.value.map((item) => (
                        <div
                          className="assignee-tag d-flex"
                          onClick={() => {
                            const newAssignees = field.value.filter(
                              (val) => val !== item
                            );
                            form.setFieldValue(field.name, newAssignees);
                          }}
                        >
                          <div>{item}</div>
                          <GrFormClose />
                        </div>
                      ))}
                    </>
                  )}
                />
              </div>
              <div className="d-flex col">
                <Field
                  id="searchAssignee"
                  name="searchAssignee"
                  placeholder="Type names of assignees to start getting suggestions"
                  component={({
                    field, // { name, value, onChange, onBlur }
                    form, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
                    ...props
                  }) => {
                    const suggestion = getMatching(users, field.value);
                    return (
                      <>
                        <input
                          className="input-fields"
                          {...props}
                          {...form}
                          {...field}
                        />
                        {suggestion && (
                          <span
                            className="text-nowrap"
                            onClick={() => {
                              if (!form.values.assignees.includes(suggestion)) {
                                form.setFieldValue("assignees", [
                                  ...form.values.assignees,
                                  suggestion
                                ]);
                              }
                            }}
                          >
                            {suggestion}
                          </span>
                        )}
                      </>
                    );
                  }}
                />
              </div>
            </Col>
          </Col>
        </Row>
        <div className="mt-2 float-end">
          <button className="btn btn-dark btn-sm" type="submit">
            Add
          </button>
          <button className="btn btn-light btn-sm ms-2" type="reset">
            Cancel
          </button>
        </div>
      </Form>
    </Formik>
  );
};

const App = ({ isModalVisible, setModalVisible }) => {
  const [users, setUsers] = useState([
    { name: "Hello World" },
    { name: "Foo Bar" }
  ]);

  return (
    <Modal show={true}>
      <Modal.Body>
        <Row>
          <div>
            <GrFormClose
              className="float-end"
              onClick={() => setModalVisible(false)}
              style={{ cursor: "pointer" }}
            />
          </div>
          <div>
            <FormContainer
              users={users}
            />
          </div>
        </Row>
      </Modal.Body>
    </Modal>
  );
};