Fetch on REST API - the beautilful way?

Hi all, I am working on moving away from HTTP and start using fetch. Case is a simple external API with a simple list exposed on a url returning some json. Sounds really simple, but I have yet not managed to elegantly integrate this API together with my existing mongo collections.

How do I get the external data to resemble a collection as much as possible, and where do I find some nice updated documentation on how to do it?

Fetch is working fine, but the async nature and the promise based returns are difficult for non-gurus.

Any tips is appreciated…

Current code in client

async function getData(url) {
      try {
        console.log("Getting data", url);
        const response = await fetch(url, {
          method: 'GET', // *GET, POST, PUT, DELETE, etc.
          mode: 'no-cors', // no-cors, *cors, same-origin
         });
        console.log("Continuing...")
        const data = await response.json();
        console.log("Some data her...", data);
        return (null, data);
      } catch (err) {
        return (err, null);
      }
    }

    const getDataCall = Meteor.wrapAsync(getData);

    const models_response = getDataCall('https://x.y.z/models');

This does not work.

you can just call

const models_response = await getData('https://x.y.z/models');
1 Like

and make sure you don’t await forever (if you call an API which is not monitored nor in your control).

Thanks gents, these might lead to a solution, still fiddling. But I am thinking that a Promise based approach would make more sense, but I have tried several things that ends up in problems.I am looking for the best way to do it in the Meteor framework. This should be something that a lot of people are looking for but I cannot find a beautiful example.

I’m not sure if the Meteor.wrapAsync works after 3.0 released.

I am doing like this now:

const [models, setData] = useState(null);
  const [loadingData, setLoadingData] = useState(true);

  useEffect(() => {
    // WIP: this call requires the proxy running!
    fetch('http://api.api.com/route1', {
      method: 'GET', // *GET, POST, PUT, DELETE, etc.
    })
      .then(response => response.json())
      .then(data => {
        Tracker.autorun(() => {
          setData(data);
          setLoadingData(false);
        });
      })
      .catch(error => {
        console.error('Error:', error);
        setLoadingData(false);
      });
  }, []);

Which does what I need. :grinning:

convention would be const [data, setData] = useState(null) or const [models, setModels] = useState(null)

Ok, your models starts with a null which basically means loading data is true (you don’t need another local state for it). You then get data and set it into the local state. Having something other than null in models means data loading ended so … it is false.
For every update of the local state, the view re-renders so … you can just remove loadingData since you can interpret it from models (e.g. loadingData = models === null)

You useEffect will only occur once, when the functional component is loaded. Tracking inside then does nothing. You can remove both the tracker and setLoadingData.
In the case of an error, you can set the models as undefined (!== null so you will know the loading has ended)

I could be wrong but I don’t think you need Tracker.autorun() function here.
Btw your implementation has potential memory leaking issue. You should check if the component is mounted before updating its state. => use AbortController() if possible (thanks @superfail for your correction)

Correct.

To check whether the component is mounted is not the recommended solution (both for old react and function component react, see https://legacy.reactjs.org/blog/2015/12/16/ismounted-antipattern.html and https://github.com/facebook/react/pull/22114#issuecomment-929191048). To really take care of the memory leak you need to “cancel” the pending request before you fire a new one. If you use an AbortController to handle timeouts as suggested above (Fetch on REST API - the beautilful way? - #3 by paulishca), you can also exploit it to record whether the corresponding request was cancelled in the useEffect “cleanup” function with AbortController#abort(). You will need to Promise#catch() the corresponding AbortError.

PS: Even if your example has an empty dependency array you need this. What if your component is unmounted before you get the request response?

1 Like