1
votes

I'm working on a form using react-hook-form that contains a react-select CreatableSelect multiselect input. The multiselect is used for tags of a given post and it is conditional based on if the user selects to update the tags of an existing post.

My issue is that the defaultValue for the multiselect is not working when a user selects an existing post that contains tags.

The overall flow is: User selects existing post (in PublicShareNetworkSelect in my example) > onChange function changes the post ID stored in hook (selectedNetwork in my example) > change in selectedNetwork fires getNetworkData function that sets the tags variable (networkTags) used as the multiselect defaultValue

Also the getTags() function is used to populate the options in the multiselect.

I believe that the issue as something to do with getting the data from the APIs because I tried to create a minimum reproducible example, but it works exactly how I want it to without the axios calls. However, when I console.log the allTags and networkTags in my full example, there are matching objects in the arrays (the matches should be the defaultValue).

Code example: Main/Parent form component

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 - the select component that triggers the function to set the existing post id (selectedNetwork):

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;

Edit 1: console.log output for allTags (options) and networkTags (defaultValue) enter image description here

1

1 Answers

1
votes

The problem is, defaultValue is cached at the first render. The same applies to defaultValues property passed to 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.

As quote from the docs suggests - you have to use reset. I've modified your example accordingly. Take a look here. As you can see I'm asynchronously resetting the form and it works.

Also, pay attention to render prop of the Controller - I'm passing down all props given, not only onChange. It's so because there are other important thingies in here (like value). By wrapping your component in Controller you have to provide onChange and value pair at least.

If you want to read more about reset take a look here.