14
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【MUI】Data Gridのソートをカスタムする

Last updated at Posted at 2024-12-19

この記事は NTTコムウェア Advent Calender 2024 20日目の記事です。

はじめに

NTTコムウェアの中原 です。
最近は SmartCloud® オーケストレータ の開発チームで React の MUI (Material-UI) を触っております。

表にデータを出力したいとき、とりあえず Data Grid を使うことが多いのですが、ソート機能のカスタマイズが少し難しかったので備忘録として整理してみます。
本記事は 公式ドキュメント と重複する部分もありますので、そちらもご覧ください。

ソートの基本

配列におけるソートの基本的な仕組みは、要素を2つ取り出して値の比較ですね。
JavaScript の sort() を用いた簡単な例を示します。
返り値がゼロ、正、負のいずれであるかによって処理が変化します。

昇順

const numbers = [5, 2, 9, 1, 5, 6]
numbers.sort((v1, v2) => v1 - v2) // ここの返り値が重要

/*
2 と 5 を比較: 2 - 5 = -3 (2を5の左へ)
9 と 6 を比較: 9 - 6 = 3 (6を9の左へ)
5 と 5 を比較: 5 - 5 = 0 (変更なし)
...
*/

console.log(numbers) // [1, 2, 5, 5, 6, 9]

降順

const numbers = [5, 2, 9, 1, 5, 6]
numbers.sort((v1, v2) => v2 - v1) // ここの返り値が重要

console.log(numbers) // [9, 6, 5, 5, 2, 1]

サンプルの準備

適当なオブジェクト配列 rowscolumns を用意し、Data Grid に出力できたら準備完了です。
x-data-grid のバージョンは 7.6.1 です。

import React from 'react'
import Box from '@mui/material/Box'
import { DataGrid } from '@mui/x-data-grid'

const rows = [
    { id: 1, name: 'Apple', price: 1000, stock: 20 },
    { id: 2, name: 'Orange', price: 2000, stock: 15 },
    { id: 3, name: 'Peach', price: 1500, stock: 10 },
    { id: 4, name: 'Banana', price: 500, stock: 30 },
    { id: 5, name: 'Grape', price: 3000, stock: 5 },
    { id: 6, name: 'Lemon', price: 2500, stock: 8 }
]

export default function App() {
    const columns = [
        { field: 'id', headerName: 'ID' },
        { field: 'name', headerName: 'Name'},
        { field: 'price', headerName: 'Price'},
        { field: 'stock', headerName: 'Stock'}
    ]
    
    return (
        <Box>
            <DataGrid
                rows={rows}
                columns={columns}
            />
        </Box>
    )
}

image.png

sortComparator の基本

sort() と同じ要領で、昇順・降順のソートを実装します。
columns の要素に sortComparator プロパティを追加し、引数 (v1,v2) と返り値 v1 - v2 を書きます。
これだけで昇順・降順の両方をカバーしています。
返り値 v1 - v2 より、昇順にしかならないようにも見えますが、降順の場合は返り値の符号が反転するよう Data Grid が処理します。
(人によっては余計なお世話かも?)

const columns = [
    { field: 'id', headerName: 'ID' },
    { field: 'name', headerName: 'Name'},
    {
        field: 'price',
        headerName: 'Price',
        sortComparator: (v1, v2) => {
          return v1 - v2
        }
    },
    { field: 'stock', headerName: 'Stock'}
  }
]

昇順・降順を明示すると次のようになります。
sortModel プロパティを用いてソート状態を管理し、昇順・降順の条件分岐ができます。
customSortComparator の返り値を色々いじってみてソートを試してみるのも良いと思います。


const customSortComparator = (sortModel) => (v1, v2) => {
    if (sortModel.length === 0) {
        return 0 // ソートしない
    } else {
        if (sortModel[0].sort === 'asc') {
            return v1 - v2 // 昇順
        } else if (sortModel[0].sort === 'desc') {
            return v1 - v2 // 降順(v2 - v1 ではないことに注意)
        }
    }
}

export default function App() {
    const [sortModel, setSortModel] = React.useState([])
    const handleSortModel = (newSortModel) => {
        setSortModel(newSortModel)
    }

    const columns = [
        { field: 'id', headerName: 'ID' },
        { field: 'name', headerName: 'Name'},
        {
            field: 'price',
            headerName: 'Price',
            sortComparator: customSortComparator(sortModel)
        },
        { field: 'stock', headerName: 'Stock'}
    ]

    return (
        <Box>
            <DataGrid
                rows={rows}
                columns={columns}
                sortModel={sortModel} // sortModelプロパティを追加
                onSortModelChange={handleSortModel} // ハンドラを追加
            />
        </Box>
    )
}

sortModel は要素数 01 の配列で、要素にはソート対象列を表す field が入ります。
image.png
console.log(sortModel) で見るとこんな感じです。
ここでは割愛しますが、列によって異なるソートロジックを与えることも可能です。

sortComparator の応用

ここまでで話が終わってしまうと、わざわざ sortComparator を使う意味がありません。
昇順・降順のソートは Data Grid に標準搭載されているからです。
ようやくスタートラインに立ちました!ということで、ここからはカスタマイズの小技を紹介します。

特定の行を固定する

例えば、最上段の行、すなわち id === 1 の行を固定してみます。
id === 1 の行が来たときに、返り値に 0 を与えるよう条件分岐します。
id を用いるため、引数に row1, row2 を追加します。

const customSortComparator = (sortModel) => (v1, v2, row1, row2) => {
    if (sortModel.length === 0) {
        return 0 // ソートしない
    } else {
        if (sortModel[0].sort === 'asc') {
            if (row1.id === 1 || row2.id === 1) {
                return 0 // 先頭行を固定
            } else {
                return v1 - v2 // 昇順
            }
        } else if (sortModel[0].sort === 'desc') {
            if (row1.id === 1 || row2.id === 1) {
                return 0 // 先頭行を固定
            } else {
                return v1 - v2 // 降順
            }
        }
    }
}

image.png

特定の行を下方に追いやる

例えば、データが歯抜けになっていて undefined で与えられる場合、式 v1-v2 を評価できません。

{ id: 3, name: 'Peach', stock: 10 } // price が定義されていない場合

ソートしたときに邪魔なので、 undefined の行を強制的に下方に送るようにします。

const customSortComparator = (sortModel) => (v1, v2) => {
    if (sortModel.length === 0) {
        return 0 // ソートしない
    } else {
        if (sortModel[0].sort === 'asc') {
            if (v1 === undefined) {
                return 1 // v1 が未定義のとき、v1 を下方へ
            } else if (v2 === undefined) {
                return -1 //  v2 が未定義のとき、v2 を下方へ
            } else {
                return v1 - v2 // 昇順
            }
        } else if (sortModel[0].sort === 'desc') {
            if (v1 === undefined) {
                return -1 // 1 ではないことに注意
            } else if (v2 === undefined) {
                return 1 // -1 ではないことに注意
            } else {
                return v1 - v2 // 降順
            }
        }
    }
}

image.png

おわりに

Data Grid は MUI の中でも主力のコンポーネントなので、機能がかなり充実しています。ただし、痒いところに手が届くような機能は Pro や Premium といった有料プラン限定です。
紹介しておいてこう言うのも憚られますが、例えば Row pinning があればソートのカスタマイズは必要ないかもしれません。

「SmartCloud(スマートクラウド)」は、NTTコムウェア株式会社の登録商標です。
その他、記載されている会社名、製品名、サービス名は、各社の商標または登録商標です。

14
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?