如何在 react-hook-form 中的 react-select 中从 useEffect 获取条件默认值?

How to have conditional defaultValues from useEffect in react-select within react-hook-form?

我正在使用包含 react-select CreatableSelect multiselect 输入的 react-hook-form 处理表单。 multiselect 用于给定 post 的标签,它是有条件的,取决于用户 select 是否更新现有 post.[=28 的标签=]

我的问题是,当用户 select 现有 post 包含标签时,multiselect 的默认值不起作用。

总体流程是:用户 select 现有 post(在我的示例中为 PublicShareNetworkSelect) > onChange 函数更改存储在挂钩中的 post ID(selectedNetwork 在我的示例中)> selectedNetwork 中的更改会触发 getNetworkData 函数,该函数设置用作 multiselect defaultValue

的标签变量 (networkTags)

另外,getTags() 函数用于填充 multiselect 中的 options

我认为这个问题与从 API 获取数据有关,因为我试图创建一个 minimum reproducible example,但它在没有 axios 调用的情况下完全按照我想要的方式工作。但是,当我在我的完整示例中 console.log allTagsnetworkTags 时,数组中有匹配的对象(匹配应该是默认值)。

代码示例:Main/Parent表单组件

import React, { useState, useEffect } from "react";
import axios from "axios";
import Form from "react-bootstrap/Form";
import { useForm, Controller } from "react-hook-form";
import CreatableSelect from "react-select/creatable";
import Button from "react-bootstrap/Button";
import PublicShareNetworkSelect from "./publicShareNetworkSelect";

function PublicShareForm(props) {
  const {
    register,
    handleSubmit,
    reset,
    control,
    errors,
    watch,
    onChange,
  } = useForm();
  const [loading, setLoading] = useState(false);
  const [selectedNetwork, setSelectedNetwork] = useState([]);
  const [allTags, setAllTags] = useState();
  const [networkTags, setNetworkTags] = useState([]);

  //Create axios instance
  const axiosSharedNetwork = axios.create();

  async function getTags() {
    const getAllTagsApi = {
      url: "/public-share/get-all-tags",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json;charset=UTF-8",
      },
      method: "GET",
    };
    await axiosSharedNetwork(getAllTagsApi)
      .then((response) => {
        const resData = response.data;
        const tags = resData.map((tag, index) => ({
          key: index,
          value: tag.tag_id,
          label: tag.name,
        }));
        setAllTags(tags);
        setLoading(false);
      })
      .catch((error) => {
        console.log(error.response);
      });
  }

  async function getNetworkData(networkId) {
    const getNetworkDataApi = {
      url: "/public-share/get-network/" + networkId,
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json;charset=UTF-8",
      },
      method: "GET",
    };
    const getNetworkTagsApi = {
      url: "/public-share/get-network-tags/" + networkId,
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json;charset=UTF-8",
      },
      method: "GET",
    };
    await axiosSharedNetwork(getNetworkDataApi)
      .then(async (response) => {
        const resData = response.data;
        //Set some variables (i.e. title, description)
        await axiosSharedNetwork(getNetworkTagsApi)
          .then(async (response) => {
            const tagResData = response.data;
            const tags = tagResData.map((tag, index) => ({
              key: index,
              value: tag.tag_id,
              label: tag.name,
            }));
            setNetworkTags(tags);
            setLoading(false);
          })
          .catch((error) => {
            console.log(error.response);
          });
      })
      .catch((error) => {
        console.log(error.response);
      });
  }

  useEffect(() => {
    getTags();
    getNetworkData(selectedNetwork);
    reset({ tags: selectedNetwork });
  }, [reset]);

  async function onSubmit(data) {
    //Handle submit stuff
  }
  console.log(allTags);
  console.log(networkTags);
  return (
    <Form id="public-share-form" onSubmit={handleSubmit(onSubmit)}>
      <Form.Group>
        <Form.Label>Create New Version of Existing Shared Network?</Form.Label>
        <PublicShareNetworkSelect
          control={control}
          onChange={onChange}
          setSelectedNetwork={setSelectedNetwork}
        />
        <Form.Label>Tags</Form.Label>
        <Controller
          name="tags"
          defaultValue={networkTags}
          control={control}
          render={({ onChange }) => (
            <CreatableSelect
              isMulti
              placeholder={"Select existing or create new..."}
              onChange={(e) => onChange(e)}
              options={allTags}
              defaultValue={networkTags}
              classNamePrefix="select"
            />
          )}
        />
      </Form.Group>
        <Button variant="secondary" onClick={props.handleClose}>
          Cancel
        </Button>
        <Button variant="primary" type="submit">
          Share
        </Button>
    </Form>
  );
}

export default PublicShareForm;

PublicShareNetworkSelect - 触发函数设置现有 post id (selectedNetwork) 的 select 组件:

import React, { useState, useEffect } from "react";
import axios from "axios";
import { Controller } from "react-hook-form";
import Select from "react-select";

function PublicShareNetworkSelect(props) {
  const [loading, setLoading] = useState(false);
  const [networks, setNetworks] = useState([]);
  //Create axios instance
  const axiosNetworks = axios.create();
  // Add a request interceptor
  axiosNetworks.interceptors.request.use(
    function (config) {
      // Do something before request is sent
      setLoading(true);
      return config;
    },
    function (error) {
      // Do something with request error
      setLoading(false);
      return Promise.reject(error);
    }
  );

  // Add a response interceptor
  axiosNetworks.interceptors.response.use(
    function (response) {
      // Any status code that lie within the range of 2xx cause this function to trigger
      // Do something with response data
      setLoading(true);
      return response;
    },
    function (error) {
      // Any status codes that falls outside the range of 2xx cause this function to trigger
      // Do something with response error
      setLoading(false);
      return Promise.reject(error);
    }
  );

  async function getNetworks() {
    const getNetworksApi = {
      url: "public-share/get-user-networks/" + props.username,
      method: "GET",
    };
    await axiosNetworks(getNetworksApi)
      .then(async (response) => {
        setNetworks(
          response.data.map((network, index) => ({
            key: index,
            value: network.network_id,
            label: network.title,
          }))
        );
        setLoading(false);
      })
      .catch((error) => {
        console.log(error.response);
      });
  }

  useEffect(() => {
    getNetworks();
  }, []);

  function handleChange(data) {
    console.log(data);
    if (data) {
      props.setSelectedNetwork(data.value);
      props.getNetworkData(data.value);
    } else {
      props.setNetworkTitle("");
      props.setNetworkDesc("");
    }
  }

  if (!loading) {
    if (networks.length === 0) {
      return (
        <React.Fragment>
          <br />
          <p className="font-italic text-muted">
            You haven't created any public networks yet.
          </p>
        </React.Fragment>
      );
    } else {
      return (
        <Controller
          name="tags"
          defaultValue={[]}
          control={control}
          render={(props) => (
            <CreatableSelect
              isMulti
              placeholder={"Select existing or create new..."}
              onChange={(e) => onChange(e)}
              // defaultValue={networkTags}
              options={allTags}
              classNamePrefix="select"
              {...props}
            />
          )}
        />
      );
    }
  } else {
    return <React.Fragment>Loading...</React.Fragment>;
  }
}

export default PublicShareNetworkSelect;

编辑 1:console.log allTags(选项)和 networkTags(默认值)的输出

问题是,defaultValue 在第一次渲染时被缓存。这同样适用于 defaultValues 属性 传递给 useForm.

Important: defaultValues is cached at the first render within the custom hook. If you want to reset the defaultValues, you should use the reset api.

正如文档中引述的那样——您必须使用 reset。我相应地修改了你的例子。看看here。如您所见,我正在异步重置表单并且它有效。

此外,请注意 Controllerrender 道具 - 我将传递给定的所有道具,而不仅仅是 onChange。之所以如此,是因为这里还有其他重要的东西(比如 value)。通过将您的组件包装在 Controller 中,您必须至少提供 onChangevalue 对。

如果您想了解更多关于 reset 的信息,请查看 here