DEV Community

Frulow
Frulow

Posted on

React Api Calls Using Web Workers - Reusable Hook

The aim is to create a reusable react hook to make api requests using axios and it should take all the axios request configs. It should also tell the status of the request: isLoading or error.


// workers/useApiWorker.ts

import React, { useCallback, useRef } from 'react';

import { AxiosRequestConfig, AxiosResponseTransformer } from 'axios';

import axios from '../../_axios';

interface Props {
  enabled: boolean;
  url: string;
  method: AxiosRequestConfig['method'];
  transformResponse?: AxiosResponseTransformer;
}

interface UseApiWorker<T> {
  data: T | null;
  isLoading: boolean;
  error: any | Error | Record<string, any>;
  refetch: (requestConfig: AxiosRequestConfig) => void;
}

type MessageEventDataType<T> =
  | {
      type: 'status';
      isLoading?: boolean;
      isError?: any | false;
    }
  | {
      type: 'response';
      data: T;
    };

export function useApiWorker<T>({
  enabled,
  url,
  method,
  transformResponse,
}: Props): UseApiWorker<T> {
  const workerRef = useRef<Worker | null>(null);

  const [data, setData] = React.useState<T | null>(null);
  const [isLoading, setIsLoading] = React.useState<boolean>(false);
  const [error, setError] = React.useState<any | null>(null);

  const getAxiosConfig = useCallback(() => {
    return { config: { ...axios.config, url }, method, transformResponse };
  }, [method, transformResponse, url]);

  React.useEffect(() => {
    if (!enabled) {
      return;
    }
    // Create the web worker
    const worker = new Worker(new URL('./api.worker.js', import.meta.url));
    workerRef.current = worker;
    worker.postMessage(getAxiosConfig());

    // Define the function to handle messages from the worker
    function handleWorkerMessage(event: MessageEvent<MessageEventDataType<T>>) {
      const data = event.data;
      console.log('Received data from worker:', data);

      if (!data) {
        return;
      }

      if (data.type === 'status') {
        if (typeof data.isLoading !== 'undefined') setIsLoading(data.isLoading);
        if (typeof data.isError !== 'undefined') setError(data.isError);
      }

      if (data.type === 'response')
        // Update your React component's state or perform other actions with the data
        setData(data.data);
    }

    // Listen for messages from the worker
    worker.onmessage = handleWorkerMessage;

    // Clean up the worker when the component unmounts
    return () => {
      worker.terminate();
      workerRef.current = null;
    };
  }, [enabled, getAxiosConfig]);

  const handleRefetch = useCallback(
    (axiosRequestConfig: AxiosRequestConfig) => {
      if (workerRef.current)
        workerRef.current.postMessage({ ...getAxiosConfig(), ...axiosRequestConfig });
    },
    [getAxiosConfig]
  );

  return { data, isLoading, error, refetch: handleRefetch };
}

export default useApiWorker;

Enter fullscreen mode Exit fullscreen mode

// workers/api.worker.js

/* eslint-disable no-restricted-globals */

import axios from 'axios';

/**
 *
 * @param {MessageEvent<{config: {defaultHeaders: {Authorization: string}, baseURL: string, url: string},} & import('axios').AxiosRequestConfig>} event
 */
self.onmessage = function ({ data: { config, ...rest } }) {
  if (!config) return;

  // Define the function to fetch data from the API
  async function fetchDataFromAPI() {
    self.postMessage({ type: 'status', isLoading: true, isError: false });
    try {
      const res = await axios.request(config.url, {
        ...rest,
        baseURL: config.baseURL,
        headers: { ...rest.headers, ...config.defaultHeaders },
      });

      self.postMessage({ type: 'response', data: res.data.package }); // Send the data back to the main thread
    } catch (error) {
      console.error(`Error: [${self.name}]`, error);
      self.postMessage({ type: 'status', isError: error }); // Send the data back to the main thread
    } finally {
      self.postMessage({ type: 'status', isLoading: false });
    }
  }

  fetchDataFromAPI();
};

Enter fullscreen mode Exit fullscreen mode

I just created it, have not tested it. But it should work. There might be minor bugs which I will fix when I will use it.

Hope it helps & Suggestions always accepted. As well as bug fixes. Thanks already!

Top comments (0)