Computing Atman

useLayoutEffect

seconds elapsed

説明

useLayoutEffectを利用したサンプルコード

補足

  • useLayoutEffectは、useEffectの前に実行される。
  • useLayoutEffectは、スクリーン更新前に実行されるため、画面のちらつきを防止することができる。
  • stateは、画面読み込み時に初期化されるため、値を残しておく場合は localStorage を利用する。
  • このサンプルでは、タイマーをlocalStarageに保存しているが、画面をリロードすると、タイマーがリセットされる。

利用するshadcn/uiコンポーネント

  • button

サンプルコード

Example.tsx

'use client';

import { Button } from '@repo/ui';
import { useEffect, useLayoutEffect, useState } from 'react';
import { Random } from './_components/Random';

const Example = () => {
  const [isDisplayed, setIsDisplayed] = useState(true);

  return (
    <div className="flex flex-col place-items-start gap-4">
      {isDisplayed && <Timer />}
      <Random />
      <Button onClick={() => setIsDisplayed((prev) => !prev)}>Toggle</Button>
    </div>
  );
};

const Timer = () => {
  const [time, setTime] = useState(0);

  useLayoutEffect(() => {
    console.log('1:useLayoutEffect start');
    const _time = parseInt(window.localStorage.getItem('time-key') ?? '');
    if (!isNaN(_time)) {
      console.log('1:useLayoutEffect setTime: ', _time);
      setTime(_time);
    }
  }, []);

  useEffect(() => {
    console.log('2:useEffect init start');
    let intervalId = window.setInterval(() => {
      console.log('2:useEffect interval called');
      setTime((prev) => prev + 1);
    }, 1000);

    return () => {
      window.clearInterval(intervalId);
      console.log('2:useEffect init end');
    };
  }, []);

  useEffect(() => {
    // After layoutEffect executed, time is 0 in the first render
    console.log('3:useEffect updated start: ', time);
    document.title = 'counter: ' + time;
    window.localStorage.setItem('time-key', time.toString());

    return () => {
      console.log('3:useEffect updated end');
    };
  }, [time]);

  return (
    <>
      <h3>
        <time>{time}</time>
        <span> seconds elapsed</span>
      </h3>
    </>
  );
};

export { Example };

Random.tsx

'use client';

import { Button } from '@repo/ui';
import { useLayoutEffect, useState } from 'react';

const Random = () => {
  const [state, setState] = useState(0);

  useLayoutEffect(() => {
    if (state === 0) {
      setState(Math.random() * 300);
    }
  }, [state]);

  return <Button onClick={() => setState(0)}>state: {state}</Button>;
};

export { Random };