🗿
MUIのDataGridにPHPでデータを渡して使ってみる
普段はjQueryのDataTablesなどを使っていて、Reactはまだ慣れないのですが、とりあえず何とか動くところまで行ったのでまとめてみます。
DataGridに渡す要素とロード処理をカスタムフックに(useGrid)
- DataGridに渡すプロパティのuseStateなどをここでまとめて処理
- リモートサーバからデータを取ってくる機能(fnLoadDataGrid)もここに実装
- fnLoadDataGrid関数にpage/pageSize/sortModel/filterModelを渡す
import { useState, useCallback } from "react";
import { GridRowsProp, GridSortModel, GridFilterModel } from "@mui/x-data-grid";
import dataLoader from "./dataLoader";
// grid用のカスタムフック
type TypeProps = {
urlGrid: string;
defaultPageSize?: number;
};
const useGrid = ({ urlGrid, defaultPageSize = 10 }: TypeProps) => {
// DataGrid関係のuseState群
const [page, onPageChange] = useState<number>(0);
const [pageSize, onPageSizeChange] = useState<number>(defaultPageSize);
const [sortModel, setSortModel] = useState<GridSortModel>([
{ field: "id", sort: "asc" }
]);
const [filterModel, setFilterModel] = useState<GridFilterModel | undefined>();
const [rows, setRows] = useState<GridRowsProp>([]);
const [rowCount, setRowCount] = useState<number>(0);
const [loading, setLoading] = useState<boolean>(false);
// フィルター変更機能
const onFilterModelChange = useCallback(
(filterModel: GridFilterModel) => {
setFilterModel(filterModel);
},
[setFilterModel]
);
// ソート変更機能
const onSortModelChange = useCallback((newSortModel: GridSortModel) => {
setSortModel(newSortModel);
}, []);
// データローディング関数
const fnLoadDataGrid = useCallback(async () => {
setLoading(true);
const res = await dataLoader(
urlGrid,
page,
pageSize,
sortModel,
filterModel
);
setLoading(false);
if (res.success) {
setRows(res.data);
setRowCount(Number(res.total));
} else {
// エラー処理
}
}, [
urlGrid,
page,
pageSize,
sortModel,
setRows,
setRowCount,
filterModel,
setLoading
]);
return {
fnLoadDataGrid,
page,
onPageChange,
pageSize,
onPageSizeChange,
sortModel,
setSortModel,
filterModel,
setFilterModel,
rows,
setRows,
rowCount,
setRowCount,
loading,
setLoading,
onSortModelChange,
onFilterModelChange
};
};
export default useGrid;
axiosでリモートサーバからデータ取得(dataLoader)
- sortModelとfilterModelはそのまま投げる
- PHP側で
$_POST
とかで受ける場合はURLSearchParams()
を使って - 戻り値の処理はリモートサーバの実装次第
import axios from "axios";
import { AxiosResponse } from "axios";
import { GridSortModel, GridFilterModel } from "@mui/x-data-grid";
const dataLoader = async (
urlGrid: string,
page: number,
pageSize: number,
sortModel: GridSortModel,
filterModel: GridFilterModel | undefined
) => {
//console.log("load data");
// awaitでaxiosでリモートからデータ呼び出して戻す
// 本当はここでpostでsortModelとかfilterModelとかを渡す
return await axios
.get(urlGrid, {
page: page,
pageSize: pageSize,
sortModel: sortModel,
filterModel: filterModel
})
.then((res: AxiosResponse) => {
const d = res.data;
return d;
})
.catch((err: any) => {
// エラー処理
console.log("error");
console.log(err);
});
};
export default dataLoader;
サーバ側(PHP)の実装
- なんかMySQLのitemテーブルみたいなのがある前提で
- page/pageSizeでLIMIT句、sortModelでORDER句、filterModelでWHERE句を作成
-
npm start
などでローカルサーバから試す場合CORSエラーにならないように設定が必要
// axiosからjson投げたリクエストを取得
$json = file_get_contents('php://input');
$p = json_decode($json);
$paging = "";
$ordering = "";
$where = array();
// paging処理
if (isset($p->page) && $p->pageSize) {
$start = $p->page * $p->pageSize;
$paging = " LIMIT {$start},{$p->pageSize}";
}
// sortModel処理
if (is_array($p->sortModel) && count($p->sortModel)) {
$sorts = array();
foreach ($p->sortModel as $sm) {
$sorts[] = "{$sm->field} {$sm->sort}";
}
$ordering = " ORDER BY " . implode(",", $sorts);
}
// filter処理
if (isset($p->filterModel)) {
// AND/OR・・・通常使わないかな・・・
$linkOp = $p->filterModel->linkOperator;
// filterModel.itemsをループして検索条件配列作成
foreach ($p->filterModel->items as $f) {
if ($f->operatorValue == 'equals' && $f->value) {
$where[] = "{$f->columnField} = '{$f->value}'";
} elseif ($f->operatorValue == 'startsWith' && $f->value) {
$where[] = "{$f->columnField} like '{$f->value}%'";
} elseif ($f->operatorValue == 'endsWith' && $f->value) {
$where[] = "{$f->columnField} like '%{$f->value}'";
} elseif ($f->operatorValue == 'isEmpty') {
$where[] = "{$f->columnField} = ''";
} elseif ($f->operatorValue == 'isNotEmpty') {
$where[] = "{$f->columnField} != ''";
} elseif ($f->value) {
$where[] = "{$f->columnField} like '%{$f->value}%'";
}
}
}
if (count($where)) {
$where = " WHERE " . implode(" AND ", $where);
}else{
$where = "";
}
// 件数
$sql = "SELECT COUNT(*) AS cnt FROM item {$where}";
$total = $pdo->query($sql)->fetchColumn();
// データ取得
$sql = "SELECT id, item_name, item_size, item_price
FROM item {$where} {$ordering} {$paging}";
$data = $pdo->query($sql)->fetchAll();
print(json_encode(array("success"=>true, "data"=>$data, "total"=>$total)));
DataGridをカスタム(DataGridWrapper)
- DataGridをカスタムコンポーネントにしておく(xxxMode系は全部server)
- 最初こっちにDataLoaderを埋め込むようにしてuseImperativehandleで親コンポーネントに戻すようにしてたけど、後々で使いづらかったのでカスタムフック側に移動した。
-
{...gridApi}
のところは何をセットしたのか忘れがちなので1行ずつ書いたほうが良いかも - 本当はCustomToolbarに「新規登録ボタン」とかを設定する
import {
DataGrid,
GridColDef,
GridToolbarColumnsButton,
GridToolbarContainer,
GridToolbarFilterButton
} from "@mui/x-data-grid";
import { Button } from "@mui/material";
import RefreshIcon from "@mui/icons-material/Refresh";
// GridToolbarにReloadボタンを追加
const CustomToolbar = ({ fnLoadDataGrid }: { fnLoadDataGrid: any }) => {
return (
<GridToolbarContainer>
<Button onClick={fnLoadDataGrid}>
<RefreshIcon fontSize="small" sx={{ ml: "-2px", mr: 1 }} />
Reload
</Button>
<GridToolbarColumnsButton />
<GridToolbarFilterButton />
</GridToolbarContainer>
);
};
/* DataGridのPropsのtype定義 */
type TypeDataGridWrapper = {
columns: GridColDef[];
fnLoadDataGrid: any;
gridApi: any;
[key: string]: any;
};
/* DataGridの拡張コンポーネント */
const DataGridWrapper = ({
columns,
fnLoadDataGrid,
gridApi,
...rest
}: TypeDataGridWrapper) => {
return (
<>
<DataGrid
components={{
Toolbar: CustomToolbar
}}
componentsProps={{
toolbar: {
fnLoadDataGrid: fnLoadDataGrid
}
}}
columns={columns}
pagination
rowsPerPageOptions={[5, 10, 20]}
paginationMode="server"
sortingMode="server"
filterMode="server"
/*
rows={gridApi.rows}
pageSize={gridApi.pageSize}
loading={gridApi.loading}
sortModel={gridApi.sortModel}
onSortModelChange={gridApi.onSortModelChange}
rowCount={gridApi.rowCount}
onCellEditCommit={gridApi.handleCellEditCommit}
onPageChange={gridApi.onPageChange}
onPageSizeChange={gridApi.onPageSizeChange}
onFilterModelChange={gridApi.onFilterModelChange}
*/
{...gridApi}
{...rest}
/>
</>
);
};
export default DataGridWrapper;
App
- 上で作っったuseGridとDataGridWrapperを使ってページ作成
- ココでcolumnsの設定とリモートサーバのURLを設定
- 実際はgetActionsの辺りに編集フォームなどを埋め込みます
import { useMemo, useEffect } from "react";
import { IconButton, Box, CssBaseline } from "@mui/material";
import { GridColDef } from "@mui/x-data-grid";
import EditIcon from "@mui/icons-material/Edit";
import DeleteIcon from "@mui/icons-material/Delete";
import useGrid from "./useGrid";
import DataGridWrapper from "./DataGridWrapper";
// メインのコンポーネント
export default function App() {
// Grid用のカスタムフック-取得先のURLはここでセット
const urlGrid = 'grid_items.php';
const { fnLoadDataGrid, ...gridApi } = useGrid({ urlGrid: urlGrid });
// 初期データローディング
useEffect(() => {
fnLoadDataGrid();
}, [fnLoadDataGrid]);
// DataGridに渡すグリッドカラムの定義
const columns: GridColDef[] = useMemo(
() => [
{ field: "id", headerName: "id" },
{ field: "item_name", headerName: "商品名", flex: 1 },
{ field: "item_size", headerName: "サイズ", width: 150 },
{ field: "item_price", headerName: "単価" },
{
field: "actions",
type: "actions",
getActions: (params: any) => {
return [
<IconButton>
<EditIcon fontSize="small" />
</IconButton>,
<IconButton>
<DeleteIcon fontSize="small" />
</IconButton>
];
}
}
],
[]
);
// *** RETURN ***
return (
<>
<CssBaseline />
<Box sx={{ height: "100vh" }}>
<DataGridWrapper
sx={{ flexGrow: 1 }}
columns={columns}
fnLoadDataGrid={fnLoadDataGrid}
gridApi={gridApi}
initialState={{
columns: {
columnVisibilityModel: {
id: false
}
}
}}
/>
</Box>
</>
);
}
ハマった所
- id列を隠すためにcolumns定義で
hide: true
と設定していて・・・initialStateで設定しなきゃダメだったんですね(ちゃんと公式に書いてました)
サンプル
- 肝心なデータ渡すところ、typicodeに置き換えてるのでソートとか出来ませんが
Discussion