useQueue Tested

A hook that allows to handle queue while maintaining the necessary state updates.

Example

Code

import { useCallback, useState } from "react";

type UseQueueActions<T> = {
  add: (element: T) => void;
  remove: () => T | undefined;
  clear: () => void;
  first: T | undefined;
  last: T | undefined;
  size: number;
};

type UseQueueReturn<T> = [Array<T>, UseQueueActions<T>];
/**
 * A hook that allows to handle queue while maintaining the necessary state updates.
 * @param  {Array<T>} initialValue - The optional initial value of the queue.
 * @returns {UseQueueReturn} An array containing the queue and an object with the following properties:
 * properties:
 * 1. add - A function to add an element to the queue.
 * 2. remove - A function to remove an element from the queue.
 * 3. clear - A function to clear the queue.
 * 4. first - The first element in the queue.
 * 5. last - The last element in the queue.
 */

export default function useQueue<T>(
  initialValue: Array<T> = [],
): UseQueueReturn<T> {
  const [queue, setQueue] = useState(initialValue);

  const add = useCallback((element: T) => {
    setQueue((q) => [...q, element]);
  }, []);

  const remove = useCallback(() => {
    let removedElement;

    setQueue(([first, ...q]) => {
      removedElement = first;
      return q;
    });

    return removedElement;
  }, []);

  const clear = useCallback(() => {
    setQueue([]);
  }, []);

  return [
    queue,
    {
      add,
      remove,
      clear,
      first: queue[0],
      last: queue[queue.length - 1],
      size: queue.length,
    },
  ];
}

Tests

import useQueue from "@/hooks/useQueue";
import { renderHook, act, cleanup, waitFor } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";

describe("useQueue", () => {
  afterEach(() => {
    cleanup();
  });

  it("should initialize without values", () => {
    const { result } = renderHook(() => useQueue());
    const [queue, actions] = result.current;
    expect(queue).toStrictEqual([]);
    expect(actions.size).toBe(0);
  });

  it("should initialize with values", () => {
    const { result } = renderHook(() => useQueue([1, 2, 3]));
    const [queue, actions] = result.current;
    expect(queue).toStrictEqual([1, 2, 3]);
    expect(actions.size).toBe(3);
  });

  it("should add an element to the queue", () => {
    const { result } = renderHook(() => useQueue([1, 2, 3]));
    act(() => {
      result.current[1].add(4);
    });
    const [queue, actions] = result.current;
    expect(queue).toStrictEqual([1, 2, 3, 4]);
    expect(actions.size).toBe(4);
    expect(actions.first).toBe(1);
    expect(actions.last).toBe(4);
  });

  it("should remove an element from the queue", () => {
    const { result } = renderHook(() => useQueue([1, 2, 3]));
    act(() => {
      result.current[1].remove();
    });
    const [queue, actions] = result.current;
    expect(queue).toStrictEqual([2, 3]);
    expect(actions.size).toBe(2);
    expect(actions.first).toBe(2);
    expect(actions.last).toBe(3);
  });

  it("should clear the queue", () => {
    const { result } = renderHook(() => useQueue([1, 2, 3]));
    act(() => {
      result.current[1].clear();
    });
    const [queue, actions] = result.current;
    expect(queue).toStrictEqual([]);
    expect(actions.size).toBe(0);
  });
});