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!

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →