What's is wrong in useTypeWriter hook algorithm

Viewed 9

I'm trying to create a type writer hook in React, but I can't identify in the algorithm what's not working. I don't know if useState is the most viable to hold all variables. The characters & and . will be rendered with styling, so I created a checkLetter function to check them and return a span styled in Tailwind. The state has a variable named writing to hold the each letter as string and a stylized span (ampersando and point) as ReactElement. The algorithm is in an infinite loop and is not adding letter by letter. I can't understand why since I put the conditionals on top of isWriting in useEffect so that only one scope is executed. When isWriting is true then the word is being written, when is false it is being deleted. That's how it should be at least. The algorithm receives delay for the beginning of each sentence, between each word and for the end of the written word until it starts deleting letter by letter. The stringsIdx serves to pull the sentence from the array of strings received in the component, and the letterIdx to pull each letter from that string, at the end of the type writer loop it goes to the next sentence until it returns to the first. The hook returns the array to be rendered in JSX.

import React, { useEffect, useState } from 'react';
import { nanoid } from '@reduxjs/toolkit';

export const useTypeWriter = (
  strings: string[],
  writingDelay: number,
  initialDelay: number,
  finalDelay: number
) => {
  const [state, setState] = useState<{
    writing: Array<React.ReactElement | string>;
    isWriting: boolean;
    letterIdx: number;
    stringsIdx: number;
  }>({
    writing: [],
    isWriting: true,
    letterIdx: 0,
    stringsIdx: 0,
  });

  const string = strings[state.stringsIdx];

  const checkLetter = (
    letter: string,
    arr: Array<React.ReactElement | string>
  ) => {
    let ampersand: React.ReactElement;
    let point: React.ReactElement;

    if (letter === '&') {
      ampersand = (
        <span key={nanoid()} className='font-bold text-primary'>
          &
        </span>
      );

      return [...arr, ampersand];
    }

    if (letter === '.') {
      point = (
        <span
          key={nanoid()}
          className='font-bold text-primary font-anton animate-pulse'
        >
          .
        </span>
      );

      return [...arr, point];
    }

    return [...arr, letter];
  };

  useEffect(() => {
    let timer;
    if (state.isWriting) {
      if (state.writing.length === string.length) {
        timer = setTimeout(
          () =>
            setState((state) => ({
              ...state,
              isWriting: false,
            })),
          initialDelay
        );
      } else {
        setTimeout(
          () =>
            setState((state) => ({
              ...state,
              writing: checkLetter(string[state.letterIdx], state.writing),
              letterIdx: state.letterIdx + 1,
            })),
          writingDelay
        );
      }
    }

    if (!state.isWriting) {
      if (state.writing.length === string.length) {
        timer = setTimeout(
          () =>
            setState((state) => ({
              ...state,
              isWriting: true,
            })),
          finalDelay
        );
      } else {
        setTimeout(
          () =>
            setState((state) => ({
              ...state,
              writing: checkLetter(string[state.letterIdx], state.writing),
              letterIdx: state.letterIdx - 1,
            })),
          writingDelay
        );
      }
    }

    console.log(state, string);

    return () => {
      clearTimeout(timer);
    };
  }, [string, state, setState, writingDelay, initialDelay, finalDelay]);

  return state.writing;
};
0 Answers
Related