[React]自動テキスト入力を実装してみた

[https://tech.nextroll.com/](https://tech.nextroll.com/)ここの自動テキスト入力の箇所を実装してみたときのメモです。

動画(画質が粗い。。)
![video.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2726391/04125ec3-c2b0-8360-c362-94a54a38f9f8.gif)


Promiseだらけになってますが、もっと簡単にできる？
カーソルは|をアニメーションで点滅させて表現しています。

```javascript:ソース(抜粋)
import React, { useState, useEffect } from "react";
import "./home/Home.css";

const MAIN_TEXT = "Join NextRoll as we build";
const SUB_TEXT1 = "modern web UIs";
const SUB_TEXT2 = "scalable data pipelines";
const SUB_TEXT3 = "open source software";
const SUB_TEXT4 = "real-time bidding";
const array_text = [SUB_TEXT1, SUB_TEXT2, SUB_TEXT3, SUB_TEXT4];

export const Test: React.FC = () => {
  const [subText, setSubText] = useState([""]);

  const addChar = (array: string[], val: string): string[] => {
    array.push(val);
    return [...array];
  };
  const delChar = (array: string[]): string[] => {
    array.pop();
    return [...array];
  };

  //SUB_TEXT1～SUB_TEXT4を受け取り、文字入力したり削除したり
  const autoTypeText = (text: string) =>
    new Promise((resolve, reject) => {
      const array = Array.from(text);
      Promise.resolve()
        .then(() => {
          //入力
          return Promise.all(
            array.map((val: string, index) => {
              return new Promise((resolve1, reject) => {
                setTimeout(() => {
                  setSubText((prev) => addChar(prev, val));
                  resolve1(true);
                }, 50 * index);
              });
            })
          );
        })
        .then(() => {
          //少し待つ
          return new Promise((resolve2) => {
            setTimeout(() => {
              resolve2(true);
            }, 1000);
          });
        })
        .then((values) => {
          //削除
          return Promise.all(
            Array.from({ length: text.length }).map((val, index) => {
              return new Promise((resolve3, reject) => {
                setTimeout(() => {
                  setSubText((prev) => delChar(prev));
                  resolve3(true);
                }, 50 * index);
              });
            })
          );
        })
        .then(() => {
          //少し待って終わる
          return new Promise(() => {
            setTimeout(() => {
              resolve(true);
            }, 500);
          });
        });
    });

  //無限ループ
  const infiniteTextLoop = () => {
    (async () => {
      for (let i = 0; ; i++) {
        await autoTypeText(array_text[i]);
        if (i === array_text.length - 1) i = -1;
      }
    })();
  };

  useEffect(() => {
    infiniteTextLoop();
  }, []);

  return (
    <div style={{ margin: "20px", fontSize: "20px" }}>
      <span style={{ marginRight: "8px" }}>{MAIN_TEXT}</span>
      <span>{subText}</span>
      <span className="CursolSpan">|</span>
    </div>
  );
};
```

```:css(抜粋)
.CursolSpan {
  color: black;
  animation-duration: 0.7s;
  animation-timing-function: ease;
  animation-delay: 0s;
  animation-iteration-count: infinite;
  animation-direction: normal;
  animation-fill-mode: none;
  animation-play-state: running;
  animation-name: blink;  
}

@keyframes blink {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
```

# 参考
Promise直列の無限ループについて
[https://zenn.dev/wintyo/articles/2973f15a265581](https://zenn.dev/wintyo/articles/2973f15a265581)