5
votes

I tried to create a function for fetching data from the server, and it works. But I am not sure if that the right way?

I created a function component to fetching data, using useState, useEffect and Async/Await :

import React, { useState, useEffect } from "react";

const Fetch = () => {
  const [data, setData] = useState(null);
  useEffect(() => {
    const fetchData = async () => {
      let res = await fetch(
        "https://api.coindesk.com/v1/bpi/currentprice.json" //example and simple data
      );
      let response = await res.json();
      setData(response.disclaimer); // parse json
      console.log(response);
    };
    fetchData();
  }, []);
  return <div>{data}</div>;
};

export default Fetch; // don't run code snippet, not working, this component must be imported in main

Where I am not sure is a place where to call the fetchData function. I do that inside useEffect? Right place? And, this call will happen only one? Because i use []?

Generally, how would you do something like this?

2
Your code looks fine. Yes, you do it in useEffect. Yes, it happens just once because you used [ ]. - Nicholas Tower
Ok, right place is below function to call her? - Milosh N.
Yes the call will happen only on component mounting because of the empty array. The array in that second argument give the hook what to watch for changes and only too apply the effect if any of those dependencies has changed. Passing in an empty tells the hook that your component does not depend on props/state and thus will only apply the hook on component mount. - Thulani Chivandikwa

2 Answers

8
votes

Overall, you are heading in the right direction. For fetching data, you'd wanna use useEffect and pass [] as a second argument to make sure it fires only on initial mount.

I believe you could benefit from decoupling fetchJson function and making it more generic, as such:

const fetchJson = async (url) => {
  const response = await fetch(url);
  return response.json();
};

const Fetch = () => {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetchJson("https://api.coindesk.com/v1/bpi/currentprice.json")
      .then(({ disclaimer }) => setData(disclaimer));
  }, []);

  return <div>{data}</div>;
};
5
votes

Another option is to use a self invoking function:

const Fetch = () => {
  const [data, setData] = useState(null);
  useEffect(() => {
    (async () => {
      let res = await fetch(
        "https://api.coindesk.com/v1/bpi/currentprice.json" //example and simple data
      );
      let response = await res.json();
      setData(response);
    })();
  }, []);
  return <div>{data}</div>;
};

The suggestion to separate out the fetch logic to a separate function is a good idea and can be done as follows:

const Fetch = () => {
  const [data, setData] = useState(null);
  useEffect(() => {
    (async () => {
      let response= await fetchData("https://api.coindesk.com/v1/bpi/currentprice.json");
      setData(response);
    })();
  }, []);
  return <div>{data}</div>;
};

const fetchData = async (url) => {
  const response = await fetch(url);
  const json = await response.json();

  return json;
};

And yet another option is to create a wrapper function around useEffect that triggers the async function for you similar to this:

export function useAsyncEffect(effect: () => Promise<any>) {
  useEffect(() => {
    effect().catch(e => console.warn("useAsyncEffect error", e));
  });
}