How can I reuse code when writting REST API with React Hooks?

Viewed 912

I am going through one tutorial of REST API and react, but I want to move a step ahead and write clean code. Therefore, I want to ask you for some help, so I could reuse hooks for different API requests.

With the tutorial, I've written this example where Hooks are used for saving API request statuses and think this is a good pattern I could reuse. Basically everything except const data = await API.getItems(token['my-token']) could be used for all/most API request I want to make. How should I reuse code with these technologies when building a clean API framework?

import { useState, useEffect } from 'react';
import { API } from '../api-service';
import { useCookies } from 'react-cookie';

function useFetch() {
    const [data, setData] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState();
    const [token] = useCookies(['my-token']);

    useEffect( ()=> {
        async function fetchData() {
            setLoading(true);
            setError();
            const data = await API.getItems(token['my-token'])
                .catch( err => setError(err))
            setData(data)
            setLoading(false)
        }

        fetchData();
    }, []);
    return [data, loading, error]
}

export { useFetch }

BIG Thanks!

2 Answers

I think you've done pretty good job, but you can improve the code by parameterizing it a bit, maybe some thing like (in case you use cookies and tokens always the same way):

import { useState, useEffect } from 'react';
import { API } from '../api-service';
import { useCookies } from 'react-cookie';

function useFetch(cookieName, promiseFactory) {
    const [data, setData] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState();
    const [token] = useCookies([cookieName]);

    useEffect( ()=> {
        async function fetchData() {
            setLoading(true);
            setError();
            const data = await promiseFactory(token[cookieName])
                .catch( err => setError(err))
            setData(data)
            setLoading(false)
        }

        fetchData();
    }, []);
    return [data, loading, error]
}

export { useFetch }

You can add cookieName or array of cookie names and also the promise, which obviously could be different. promiseFactory will accept the token and do its specific job (token) => Promise in order to encapsulate the token retrieval logic in the hook, that is the token[cookieName] part.

Usage would be:

function SomeComponent {
        const promiseFactory = (token) => API.getItems(token);
    
        const [data, loading, error] = useFetch('my-token', promiseFactory );
    }

If the cookies logic and token retrieval is also specific only to the case you provided, you can move it outside of the hook like:

function SomeComponent {
    const [token] = useCookies(['my-token']);

    const promise = API.getItems(token['my-token']);

    const [data, loading, error] = useFetch(promise);
}

That second approach would totally abstract away the details about how the request is constructed.

Looks like you already have a custom hook. You can just use it in whichever component you want.

Basically everything except const data = await API.getItems(token['my-token']) could be used for all/most API request I want to make.

You can send some arguments to your custom hook and dynamically send requests based on those parameters.

function useFetch(someArgument) {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState();
  const [token] = useCookies(["my-token"]);

  useEffect(() => {
    async function fetchData() {
      setLoading(true);
      setError();
      if (someArgument === 'something') {
        const data = await API.getItems(token["my-token"]).catch((err) =>
          setError(err)
        );
      } else {
        // do something else
      }
      setData(data);
      setLoading(false);
    }

    fetchData();
  }, []);
  return [data, loading, error];
}

Using the custom hook in a component:

import React from "react";

export default ({ name }) => {
  const [data, loading, error] = useFetch('something');
  
  return <h1>Hello {name}!</h1>;
};
Related