Description
A React hook that manages the state in a queue data structure.
A queue works based on the first-in, first-out (FIFO) principle.
Code Byte
import { useState, useCallback } from 'react';
interface QueueOptions<T> {
  initialValues?: T[];
}
export interface QueueState<T> {
  queue: T[];
  length: number;
  enqueue: (...items: T[]) => void;
  dequeue: () => void;
  updateQueue: (fn: (current: T[]) => T[]) => void;
  clearQueue: () => void;
  peek: () => T | undefined;
}
export const useQueue = <T>({
  initialValues = []
}: QueueOptions<T> = {}): QueueState<T> => {
  const [queue, setState] = useState<T[]>(initialValues);
  /**
   * Enqueue; Adds item to queue.
   */
  const enqueue = useCallback((...items: T[]) => {
    setState((current) => [...current, ...items]);
  }, []);
  /**
   * Dequeue; Removes first item in queue.
   */
  const dequeue = useCallback(() => {
    // Immutable solution to remove first element in array.
    setState((current) => current.slice(1));
  }, []);
  /**
   * Update function to allow for more granular control of updating the queue.
   * The `update` function accepts a callback fn where the first arg is the
   * current queue state that we can then manipulate. See tests for examples.
   * */
  const updateQueue = useCallback(
    (fn: (current: T[]) => T[]) => setState((current) => fn([...current])),
    []
  );
  /**
   * Remove all items from queue.
   */
  const clearQueue = useCallback(() => setState(() => []), []);
  /**
   * Returns the first item in the queue.
   */
  const peek = useCallback(() => {
    if (queue.length > 0) {
      return queue[0];
    }
    return undefined;
  }, [queue]);
  return {
    queue,
    length: queue.length,
    enqueue,
    dequeue,
    updateQueue,
    clearQueue,
    peek
  };
};
export default useQueue;
Arguments
| Arguments | Type | Default | Required | Description | 
|---|
| initialValues | Array<any> | [] | ❌ | An array of initial values for the queue. | 
Returns
| Returns | Type | Description | 
|---|
| queue | Array<any> | The ordered list of items. | 
| length | number | The length of the queue. | 
| enqueue | function | Adds item to queue. | 
| dequeue | function | Removes first item in queue. | 
| updateQueue | function | Update function to allow for more granular control of updating the queue. The updatefunction accepts a callback fn where the first arg is the current queue state that we can then manipulate. See tests for examples. | 
| clearQueue | function | Remove all items from queue. | 
| peek | function | Returns the first item in the queue.. | 
Usage
import { useQueue, useSafeState } from '@/react-hooks';
const MyComponent = () => {
  const { queue, enqueue, dequeue, updateQueue, clearQueue, peek } = useQueue({
    initialValues: [1, 2, 3]
  });
  const addItem = (item) => queue(item);
  const removeItem = (item) => dequeue();
  const removeOddNumbers = (item) => {
    updateQueue((items) => items.filter((item) => item % 2 === 1));
  };
  const clearAllItems = () => clearQueue();
  const getFirstItem = () => peek();
  return (
    <ul>
      {queue.map((item) => (
        <li>{item}</li>
      ))}
    </ul>
  );
};
Tests
import { renderHook, act, RenderResult } from '@testing-library/react-hooks';
import useQueue, { QueueState } from './useQueue.hook';
const initialValues = [1, 2, 3];
function assertQueue(
  result: RenderResult<QueueState<number>>,
  expectedQueue: number[],
  expectedLength: number,
  expectedPeek: number | undefined
) {
  expect(result.current.queue).toEqual(expectedQueue);
  expect(result.current.length).toEqual(expectedLength);
  expect(result.current.peek()).toEqual(expectedPeek);
}
describe('useQueue', () => {
  it('should return an empty array when no initialValues are passed', () => {
    const { result } = renderHook(() => useQueue({}));
    expect(result.current.queue).toEqual([]);
  });
  it('should set initialValues for queue', () => {
    const { result } = renderHook(() => useQueue({ initialValues }));
    assertQueue(result, [1, 2, 3], 3, 1);
  });
  it('should add values to queue', () => {
    const { result } = renderHook(() => useQueue({ initialValues }));
    act(() => {
      result.current.enqueue(4);
    });
    assertQueue(result, [1, 2, 3, 4], 4, 1);
    act(() => {
      result.current.enqueue(5);
    });
    assertQueue(result, [1, 2, 3, 4, 5], 5, 1);
  });
  it('should remove values from queue', () => {
    const { result } = renderHook(() => useQueue({ initialValues }));
    act(() => {
      result.current.dequeue();
    });
    assertQueue(result, [2, 3], 2, 2);
    act(() => {
      result.current.dequeue();
    });
    assertQueue(result, [3], 1, 3);
  });
  it('should use update fn to add value to queue', () => {
    const { result } = renderHook(() => useQueue({ initialValues }));
    const valueToAdd = 4;
    // Example of how we could use the update fn and pass it a callback
    // for a more granular control of updating the current queue state.
    function updateAddCallback(currentQueue: number[]) {
      const hasValue = currentQueue.some((item) => item === valueToAdd);
      if (hasValue) {
        return currentQueue;
      }
      return [...currentQueue, valueToAdd];
    }
    // Use update fn to add a value if it does not exist in the queue
    // Should ADD value to queue since it does NOT EXIST in queue.
    act(() => {
      result.current.updateQueue(updateAddCallback);
    });
    assertQueue(result, [1, 2, 3, 4], 4, 1);
    // Use update fn to add a value if it does not exist in the queue
    // Should NOT ADD value to queue since it does EXIST in queue
    act(() => {
      result.current.updateQueue(updateAddCallback);
    });
    assertQueue(result, [1, 2, 3, 4], 4, 1);
  });
  it('should use update fn to remove value to queue', () => {
    const { result } = renderHook(() => useQueue({ initialValues }));
    const valueToRemove = 2;
    const callbackFn = jest.fn();
    // Another example of how we could use the update fn and pass it a callback
    // for a more granular control of updating the current queue state.
    function updateRemoveCallback(currentQueue: number[]) {
      return currentQueue.filter((item) => {
        if (item === valueToRemove) {
          callbackFn();
          return false;
        }
        return true;
      });
    }
    // Use update fn to remove a value if it exists in the queue
    // Should REMOVE value from queue and call callbackFn
    act(() => {
      result.current.updateQueue(updateRemoveCallback);
    });
    assertQueue(result, [1, 3], 2, 1);
    expect(callbackFn).toHaveBeenCalledTimes(1);
    // Use update fn to remove a value if it exists in the queue
    // Should NOT REMOVE value from queue and NOT call callbackFn
    act(() => {
      result.current.updateQueue(updateRemoveCallback);
    });
    // Results should be the same as previous assertions
    assertQueue(result, [1, 3], 2, 1);
    expect(callbackFn).toHaveBeenCalledTimes(1);
  });
  it('should clear all values in queue', () => {
    const { result } = renderHook(() => useQueue({ initialValues }));
    act(() => {
      result.current.clearQueue();
    });
    assertQueue(result, [], 0, undefined);
  });
  it('should return first value in queue when calling peek', () => {
    const { result } = renderHook(() => useQueue({ initialValues }));
    let peekResult;
    act(() => {
      peekResult = result.current.peek();
    });
    expect(peekResult).toEqual(1);
  });
  it('should return the length of the queue', () => {
    const { result } = renderHook(() => useQueue({ initialValues }));
    expect(result.current.length).toEqual(3);
  });
});