Reactで再レンダリングを考えずにコンポーネントを作成してはいけない

コンポーネント作成時は再レンダリングを考える

Reactを使用しているWebサイトで、たまにReactで再レンダリングを考慮せずにコンポーネントを作成されているケースがある。

例えば以下のコードの場合は、No.1, No.2, No.3で表示されず、 再レンダリングでNo.2, No.4, No.6と表示されます。(React.StrictModeの場合)

TSX
let num = 0

function Order() {
  num = num + 1
  return <h1>No.{num}</h1>
}

export default function App() {
  return (
    <>
      <Order />
      <Order />
      <Order />
    </>
  )
}
React 実行結果

Reactで再レンダリング サンプル1

これを防ぐには、コンポーネントにpropsを渡して、再レンダリングで値が変わらないようにします。

TSX
type OrderProps = {
  num: number
}

function Order({ num }: OrderProps) {
  return <h1>No.{num}</h1>
}

export default function App() {
  return (
    <>
      <Order num={1} />
      <Order num={2} />
      <Order num={3} />
    </>
  );
}

forでループ処理する場合は配列にpushして格納したものをreturnします。

TSX
type OrderProps = {
  num: number
}

function Order({ num }: OrderProps) {
  return <h1>No.{num}</h1>
}

export default function App() {
  const orders = []
  for (let i = 1; i <= 99; i++) {
    orders.push(<Order key={i} num={i} />)
  }
  return orders
}

Reactで再レンダリング サンプル2

コード量が増えますが、<Order /> にnumなどの属性を付けないでnumを増やしたい場合はuseState, useEffect, useRefを利用すれば実装できます。

TSX
import { useState, useEffect, useRef } from 'react'

let globalNum = 0

const Order = () => {
  const [num, setNum] = useState(0)
  const isFirstRender = useRef(true)

  useEffect(() => {
    if (isFirstRender.current) {
      globalNum++
      setNum(globalNum)
      isFirstRender.current = false
    }
  }, [])

  return <h1>No.{num}</h1>
}

export default function App() {
  return (
    <div>
      <Order />
      <Order />
      <Order />
    </div>
  )
}

Reactで再レンダリング サンプル3

React初心者やReact.StrictModeを使用していない環境だと、再レンダリングを考慮しないミスは結構多いので、Reactでコンポーネントを作成する際は注意が必要です。