MUIライブラリは大量のCheckboxでチェックの表示が遅くなる

大量のCheckboxでチェックの表示が遅くなる

MUIライブラリとはReact用のUIコンポーネントライブラリです。

MUI: The React component library you always wanted

MUIを使用することでMaterial Designに基づくスタイルが簡単に作れて、Reactとの相性も良いのでよく利用されます。

便利なライブラリですが、入力フォームのCheckboxコンポーネントは大量に使用するとチェックボックスを押下した際のチェックのオン・オフの表示が遅くなります。

例えば画面上に2000個のCheckboxコンポーネントを配置してクリックすると、クリックしてからチェックボックスが付くまで時間がかかります。

この表示が遅くなる現象はメモ化やuseStateの分割では解決できません。

App.tsx
import { useState } from 'react'
import { Box, Checkbox, FormControlLabel, Typography } from '@mui/material'
import Grid from '@mui/material/Grid2'

function App() {
  const totalCheckboxes = 2000
  const [checkedCount, setCheckedCount] = useState(0)

  const handleCheckboxChange = (isChecked: boolean) => {
    setCheckedCount((prevCount) => prevCount + (isChecked ? 1 : -1))
  }

  return (
    <Box sx={{ padding: 2 }}>
      <Typography
        variant="h6"
        gutterBottom
        sx={{ position: 'fixed', bottom: 2, background: 'white', zIndex: 2 }}
      >
        Checked Count: {checkedCount} / {totalCheckboxes}
      </Typography>
      <Grid container spacing={2}>
        {Array.from({ length: totalCheckboxes }, (_, i) => (
          <Grid size={3} key={i}>
            <Box
              sx={{
                padding: 1,
                border: '1px solid #ccc',
                borderRadius: 4,
                textAlign: 'left',
              }}
            >
              <FormControlLabel
                control={
                  <Checkbox
                    onChange={(e) => handleCheckboxChange(e.target.checked)}
                  />
                }
                label={`Checkbox ${i + 1}`}
              />
            </Box>
          </Grid>
        ))}
      </Grid>
    </Box>
  )
}

export default App

MUIのCheckboxコンポーネントを2000個配置したサンプル

※ Checkboxコンポーネントが2000個あるので、Stackbritzだと表示されるのに時間がかかります。

※ 低スペックPCだと500個でもかなり表示が遅くなります。

カスタムCheckboxで表示が遅くなるのを回避する

MUIライブラリは大量のCheckboxでチェックの表示が遅くなる問題は、チェックボックスのコンポーネントを自作で作成して利用することで回避できます。

TSX
import React, { useState } from 'react'
import { Box, Typography } from '@mui/material'
import Grid from '@mui/material/Grid2'

type CustomCheckboxProps = {
  checked?: boolean
  onChange?: (checked: boolean) => void
  label?: string
};

export const CustomCheckbox: React.FC<CustomCheckboxProps> = ({
  checked = false,
  onChange,
  label,
}) => {
  const [isChecked, setIsChecked] = useState<boolean>(checked)

  const handleToggle = () => {
    const newChecked = !isChecked

    setIsChecked(newChecked)
    if (onChange) {
      onChange(newChecked)
    }
  }

  return (
    <Box
      sx={{
        display: 'flex',
        alignItems: 'center',
        cursor: 'pointer',
        userSelect: 'none',
      }}
      onClick={handleToggle}
    >
      <Box
        sx={{
          width: 12,
          height: 12,
          border: '2px solid #666',
          borderRadius: 0.5,
          backgroundColor: isChecked ? '#1976d2' : '#fff',
          borderColor: isChecked ? '#1976d2' : '#666',
          color: '#fff',
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
          transition: 'background-color 0.3s ease',
          fontSize: '0.7rem',
          fontWeight: 'bold',
        }}
      >
        {isChecked && <></>}
      </Box>
      {label && (
        <Typography
          sx={{ marginLeft: 1, color: '#333', fontSize: 16 }}
          component="span"
        >
          {label}
        </Typography>
      )}
    </Box>
  )
}

function App() {
  const totalCheckboxes = 1000
  const [checkedCount, setCheckedCount] = useState<number>(0)

  const handleCheckboxChange = (isChecked: boolean) => {
    console.log(isChecked)
    setCheckedCount((prevCount) => prevCount + (isChecked ? 1 : -1))
  }

  return (
    <Box sx={{ padding: 2 }}>
      <Typography
        variant="h6"
        gutterBottom
        sx={{ position: 'fixed', bottom: 2, background: 'white', zIndex: 2 }}
      >
        Checked Count: {checkedCount} / {totalCheckboxes}
      </Typography>
      <Grid container spacing={2}>
        {Array.from({ length: totalCheckboxes }, (_, i) => (
          <Grid size={3} key={i}>
            <Box
              sx={{
                padding: 1,
                border: '1px solid #ccc',
                borderRadius: 4,
                textAlign: 'left',
              }}
            >
              <CustomCheckbox
                onChange={handleCheckboxChange}
                label={`Checkbox ${i + 1}`}
              />
            </Box>
          </Grid>
        ))}
      </Grid>
    </Box>
  )
}

export default App

自作のCheckboxコンポーネントを2000個配置したサンプル

サンプルのリンク先を確認すると、自作のCheckboxコンポーネントの方はクリックするとすぐにチェック✔が表示されて、軽快に動作することが確認できます。

この自作のCheckboxコンポーネントではinputタグを入れていないので、form内で使用するなどで必要な場合は別途追加してください。