remirror/remirror

View on GitHub
packages/remirror__react-components/src/find-replace/use-find-replace.tsx

Summary

Maintainability
A
2 hrs
Test Coverage
import { useCallback, useEffect, useState } from 'react';
import type { FindExtension } from '@remirror/extension-find';
import { useCommands, useHelpers } from '@remirror/react-core';

interface FindReplaceState {
  query: string;
  replacement: string;
  activeIndex: number | null;
  total: number;
  caseSensitive: boolean;
}

function initialState(): FindReplaceState {
  return {
    query: '',
    replacement: '',
    activeIndex: null,
    total: 0,
    caseSensitive: false,
  };
}

type UseFindReplaceReturn = FindReplaceState & {
  findNext: () => void;
  findPrev: () => void;
  stopFind: () => void;
  replace: () => void;
  replaceAll: () => void;
  toggleCaseSensitive: () => void;
  setQuery: (query: string) => void;
  setReplacement: (replacement: string) => void;
};

export function useFindReplace(): UseFindReplaceReturn {
  const helpers = useHelpers<FindExtension>();
  const commands = useCommands<FindExtension>();
  const [state, setState] = useState<FindReplaceState>(initialState);

  const find = useCallback(
    (indexDiff = 0): void => {
      setState((state): FindReplaceState => {
        const { query, caseSensitive, activeIndex } = state;
        const result = helpers.findRanges({
          query,
          caseSensitive,
          activeIndex: activeIndex == null ? 0 : activeIndex + indexDiff,
        });
        return { ...state, total: result.ranges.length, activeIndex: result.activeIndex ?? 0 };
      });
    },
    [helpers],
  );

  const findNext = useCallback(() => find(+1), [find]);
  const findPrev = useCallback(() => find(-1), [find]);

  const stopFind = useCallback(() => {
    setState(initialState());
    commands.stopFind();
  }, [commands]);

  const replace = useCallback((): void => {
    const { query, replacement, caseSensitive, activeIndex } = state;
    commands.findAndReplace({ query, replacement, caseSensitive, index: activeIndex ?? undefined });

    const isQuerySubsetOfReplacement = caseSensitive
      ? replacement.includes(query)
      : replacement.toLowerCase().includes(query.toLowerCase());

    if (isQuerySubsetOfReplacement) {
      findNext();
    } else {
      find();
    }
  }, [state, commands, findNext, find]);

  const replaceAll = useCallback((): void => {
    const { query, replacement, caseSensitive } = state;
    commands.findAndReplaceAll({ query, replacement, caseSensitive });
    find();
  }, [commands, state, find]);

  const toggleCaseSensitive = useCallback(() => {
    setState((state) => ({ ...state, caseSensitive: !state.caseSensitive }));
  }, []);
  const setQuery = useCallback((query: string) => {
    setState((state) => ({ ...state, query }));
  }, []);
  const setReplacement = useCallback((replacement: string) => {
    setState((state) => ({ ...state, replacement }));
  }, []);

  useEffect(() => {
    find();
  }, [find, commands, state.query, state.caseSensitive]);

  return {
    ...state,
    findNext,
    findPrev,
    stopFind,
    replace,
    replaceAll,

    toggleCaseSensitive,
    setQuery,
    setReplacement,
  };
}