skip to Main Content

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

2

Answers


  1. Issue

    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.

    Solution Suggestion

    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:

    Login or Signup to reply.
  2. The error A non-serializable value was detected in an action occurs because Redux requires all actions and state to be serializable. In your implementation, the onRowSelectionChange method from material-react-table is passing non-serializable data (like functions or internal properties) into the setSelectedState function, which Redux then attempts to store.

    Problem

    The onRowSelectionChange function provided by material-react-table contains non-serializable values in its payload, and when you directly dispatch it to Redux, it causes the error.

    Solution

    You need to ensure that only serializable data (such as row IDs or a simplified version of the selection state) is dispatched to Redux.

    Updated Code

    Here’s how you can fix this issue:

    1. Extract Only Serializable Data:

    • Use getRowId to extract row IDs and store them in the Redux state.
    • Modify onRowSelectionChange to process the selection before dispatching it to Redux.

    2. Update Redux Slice:

    • Store only serializable data (like an array of selected row IDs) in the Redux slice

    1. Updated MaterialTable Component

    const MaterialTable = ({ columns = [], data = [], isSelectable = false }: any) => {
      const dispatch = useDispatch();
    
      // Redux state
      const selectedState = useSelector((state) => state.filters.searchTypeSelection);
    
      const table = useMaterialReactTable({
        columns,
        data,
        enableRowSelection: isSelectable,
        getRowId: (row) => row.key, // Ensure row.key uniquely identifies rows
        onRowSelectionChange: (selection) => {
          // Extract row IDs as a serializable state
          const selectedRowIds = Object.keys(selection);
          dispatch(setSearchTypeSelection(selectedRowIds));
        },
        state: {
          rowSelection: selectedState.reduce((acc, id) => {
            acc[id] = true;
            return acc;
          }, {}),
        },
      });
    
      return <MaterialReactTable table={table} />;
    };
    
    export default MaterialTable;
    

    2. Redux Slice Example

    Here’s how the Redux slice should handle the serialized data:

    import { createSlice } from "@reduxjs/toolkit";
    
    const initialState = {
      searchTypeSelection: [], // Store selected row IDs as an array
    };
    
    const filtersSlice = createSlice({
      name: "filters",
      initialState,
      reducers: {
        setSearchTypeSelection: (state, action) => {
          state.searchTypeSelection = action.payload; // Save only row IDs
        },
      },
    });
    
    export const { setSearchTypeSelection } = filtersSlice.actions;
    
    export default filtersSlice.reducer;
    

    Explanation of Changes

    1. Extracting Serializable Data:

      • In onRowSelectionChange, the selection object is mapped to a list of row IDs (Object.keys(selection)).
      • These IDs are serializable and can be safely stored in Redux.
    2. Syncing Redux State with MaterialReactTable:

      • The state.rowSelection property in useMaterialReactTable is derived from Redux state by converting the array of IDs into an object (expected by the library).
    3. Redux State Structure:

      • The Redux slice now maintains a simple, serializable array of row IDs (searchTypeSelection).
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search