I am working with material-react-table v3 and this my implementation:
import {MaterialReactTable,useMaterialReactTable} from "material-react-table";
const MaterialTable = ({
columns = [],
data = [],
selectedState,
setSelectedState,
isSelectable = false,
}: any) => {
const table = useMaterialReactTable({
columns,
data,
enableRowSelection: isSelectable,
getRowId: (row) => row.key,
onRowSelectionChange: setSelectedState,
state: { rowSelection: selectedState },
});
return <MaterialReactTable table={table} />;
};
When working with useState hook and passing it as props for rowSelection state and for onRowSelectionChange , everything is working fine .:
const [materialState, setMaterialState] = useState({});
selectedState={materialState}
setSelectedState={setMaterialState}
But I want to use Redux for state management, so I was trying this :
const filters = useSelector((state) => state.filters);
const dispatch = useDispatch();
selectedState={filters.searchTypeSelection}
setSelectedState={(value: any) =>
dispatch(setSearchTypeSelection(value))
}
On this I am getting this error :
A non-serializable value was detected in an action, in the path: payload. Value: old => { var _opts$selectChildren; value = typeof value !== 'undefined' ? value : !isSelected; if (row.getCanSelect() && isSelected === value) { return old; … Take a look at the logic that dispatched this action: {type: 'filters/setSearchTypeSelection', payload: ƒ} (See https://redux.js.org/faq/actions#why-should-type-be-a-string-or-at-least-serializable-why-should-my-action-types-be-constants) (To allow non-serializable values see: https://redux-toolkit.js.org/usage/usage-guide#working-with-non-serializable-data)
I want to know what is causing this issue and how can I implement state management using redux with material-react-table?
Thank you for your help.
Here is my code example :
https://codesandbox.io/p/devbox/material-react-table-redux-lm6r86
The basic issue stems from the fact that setSelectedState
is being passed a function that is passed an updater function.
export type Updater<T> = T | ((old: T) => T);
export type OnChangeFn<T> = (updaterOrValue: Updater<T>) => void;
export type RowSelectionState = Record<string, boolean>;
...
onRowSelectionChange?: OnChangeFn<RowSelectionState>;
Effectively the (old: T) => T
signature because onRowSelectionChange
is an OnChangeFn
function.
src/MaterialTable.jsx
const MaterialTable = ({
columns = [],
data = [],
selectedState,
setSelectedState, // <--
isSelectable = false,
}) => {
const table = useMaterialReactTable({
columns,
data,
enableRowSelection: isSelectable,
getRowId: (row) => row.key,
onRowSelectionChange: setSelectedState, // <--
state: { rowSelection: selectedState },
});
return <MaterialReactTable table={table} />;
};
It's this callback function that is passed as the action payload and is non-serializable. This works with the materialState
React state because the setMaterialState
state updater function also has an overloaded function signature, one of which takes the current state value and returns the next state value, e.g. (currentState: T) => T
, and doesn't care about value serializability.
A trivial fix is to simply invoke this function prior to dispatching the setSearchTypeSelection
action.
setSelectedState={(value) => {
dispatch(setSearchTypeSelection(value()));
}}
But this fails to consider any previous/old table state values. While you could manage all this yourself manually within your setSearchTypeSelection
reducer case it raises a few concerns, but namely duplication of table state logic, e.g. both in MaterialReactTable
and in your reducer logic.
I suggest allowing the function to be passed through and invoked in the setSearchTypeSelection
reducer case, passing the current state value to the callback stored in the action payload. This simply lets MaterialReactTable
handle the update logic.
src/filtersSlice.js
export const filterSlice = createSlice({
name: "filters",
initialState: { searchTypeSelection: {} },
reducers: {
setSearchTypeSelection: (state, action) => {
state.searchTypeSelection = action.payload(state.searchTypeSelection);
},
},
});
Now this just leaves the Redux serializability issue, which is trivially addressed by configuring the serializability check to ignore the setSearchTypeSelection
action's payload value.
src/store.js
import { configureStore } from "@reduxjs/toolkit";
import filtersReducer, { setSearchTypeSelection } from "./filtersSlice";
export default configureStore({
reducer: {
filters: filtersReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [setSearchTypeSelection.type],
},
}),
});
For more details and information on customizing the Redux Middleware see: