Reactjs: how to call useFetch after a variable is set

I am writing a reactjs web app where I need to initially load a list of resorts, and then, after the user selects one of them, load all the details of thet resort.

This is my useFetch function:

const useFetch = (url, options) => {
  const [response, setResponse] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [isLoading, setIsLoading] = React.useState(false);
  React.useEffect(() => {
    (async () => {
      setIsLoading(true);
      try {
        const res = await fetch(url, options);
        const json = await res.json();
        setResponse(json);
        setIsLoading(false);
      } catch (error) {
        setError(error);
        setIsLoading(false);
      }
    })();
  }, []);
  return [ response, error, isLoading ];
};

In my component, I use it to retrieve the resorts list, without any problem:

function App() {
  const [resorts, resortsIsLoading, resortsError] = useFetch(config.API_URL_GET_RESORTS);
  const [resortId, setResortId] = useState(null);

  const handleChangeResort = (event) => {
    const id = event.target.value;
    setResortId(id);
  };

  return (
    <>
      <Select
        labelId="resort-label"
        id="resort"
        label="resort"
        value={resort ? resort.id : ""}
        onChange={handleChangeResort}
      >
        {resorts.length ? (
          resorts.map(c => (
            <MenuItem key={c.id} value={c.id}>
              {c.name}
            </MenuItem>
          ))
        ) : (
          <MenuItem key={0} value={""}>
            <em>loading resorts list...</em>
          </MenuItem>
        )}
      </Select>
    </>
  )}
}

The problem is that I cannot add a useFetch to get the selected resort details after the first useFetch call:

  const [resorts, resortsIsLoading, resortsError] = useFetch(config.API_URL_GET_RESORTS);
  const [resort, resortIsLoading, resortError] = useFetch(config.API_URL_GET_RESORT, {id: resortId});

because I don’t yet have the resortId, initially.

I would need something like:

  useEffect(() => {
    if (resortId !== null) {
      const [resort, resortIsLoading, resortError] = useFetch(config.API_URL_GET_RESORT, {id: resortId});
    }
  }, [resortId]);

but hooks cannot be used inside useEffect()

What solution should I adopt to use the useFetch() logic (which I like very much, because it hides the details of the fetch from the mail app logic) in a conditional way (i.e.: after the resortId is available) ?

Answer

Your hook can provide a function to fetch/refetch the data:

const useFetch = (url, options) => {
  const [response, setResponse] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [isLoading, setIsLoading] = React.useState(false);

  const fetchData = async (resortId) => {
    setIsLoading(true);

    try {
      const res = await fetch(url, {id: resortId});
      const json = await res.json();
      setResponse(json);
      setIsLoading(false);
    } catch (error) {
      setError(error);
      setIsLoading(false);
    }
  }

  return [
    response, 
    error, 
    isLoading,
    fetchData
  ];
};

You can then call fetchData whenever you want to update your search:

const App = () => {
  const [resort, resortIsLoading, resortError, fetchResort] = useFetch(config.API_URL_GET_RESORT);


 const handleChangeResort = (event) => {
    const id = event.target.value;
    fetchResort(id);
 };

  return (
    <>
      <Select
        labelId="resort-label"
        id="resort"
        label="resort"
        value={resort ? resort.id : ""}
        onChange={handleChangeResort}
      >
        {resorts.length ? (
          resorts.map(c => (
            <MenuItem key={c.id} value={c.id}>
              {c.name}
            </MenuItem>
          ))
        ) : (
          <MenuItem key={0} value={""}>
            <em>loading resorts list...</em>
          </MenuItem>
        )}
      </Select>
    </>
  )}
}