0

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) ?

1
  • First, change the last line of your useFetch to return [ response, isLoading, error ]; as you are using array everywhere when calling useFetch Commented Oct 21, 2020 at 8:55

2 Answers 2

2

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>
    </>
  )}
}
Sign up to request clarification or add additional context in comments.

Comments

1

I would do something like so:

const useFetch = (url) => {
  const [state, setState] = React.useState([null, false, null]);

  const fetchData = async (options) => {
    setState([null, true, null]);

    try {
      const res = await fetch(url, options);
      const json = await res.json();
      setState([json, false, null]);
    } catch (error) {
      setState([null, false, error]);
    }
  }

  return [
    ...state, // response, loading, error
    fetchData
  ];
};

And then, you could use:

const [resorts, resortsIsLoading, resortsError, fetchResorts] = useFetch(config.API_URL_GET_RESORTS);
const [resort, resortIsLoading, resortError, fetchResort] = useFetch(config.API_URL_GET_RESORT);

useEffect(() => {
  fetchResorts()
}, [])

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.