{"id":234,"date":"2025-07-08T15:08:45","date_gmt":"2025-07-08T09:38:45","guid":{"rendered":"https:\/\/musikaar.com\/blog\/?p=234"},"modified":"2025-08-13T15:34:00","modified_gmt":"2025-08-13T10:04:00","slug":"creating-a-table-component-using-the-context-api","status":"publish","type":"post","link":"https:\/\/musikaar.com\/blog\/uncategorized\/creating-a-table-component-using-the-context-api\/","title":{"rendered":"Creating a table component using the context API."},"content":{"rendered":"\n<h1 class=\"wp-block-heading\"><\/h1>\n\n\n\n<p>When creating enterprise-grade data tables using React, user experience and state management are critical. Making sure the grid retains filters, sorting, pagination, and chosen rows is a frequent problem, particularly when navigating between pages or components.<\/p>\n\n\n\n<p>We&#8217;ll demonstrate how to use the Context API to create a reliable, permanent AG Grid table in this blog. Your AG Grid tables will feel clean and user-friendly with this configuration, which will assist them maintain their state across renderings and navigation.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 Persistent filters, sort, pagination, selected rows<\/li>\n\n\n\n<li>\u2705 Context API for shared state<\/li>\n\n\n\n<li>\u2705 Seamless integration for complex apps and modals<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Why Context API?<\/h2>\n\n\n\n<p>AG Grid is powerful\u2014but managing its complex state (especially across multiple pages or components) can get messy. The Context API lets us maintain a global reference to AG Grid&#8217;s APIs, while tracking table-specific state (like selected filters) in a centralized reducer.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcc1 agGridContext.tsx<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code><mark style=\"background-color:#fff\" class=\"has-inline-color has-black-color\">\n\/\/ Context and reducer for AG Grid\nimport React, { createContext, useCallback, useContext, useReducer, useRef, useState } from \"react\";\nimport { ColumnState, GridApi, RowNode } from \"ag-grid-community\";\n\n\/\/ Define State Structure\ninterface PageState {\n    selectedRows: RowNode&#91;];\n    selectedFilters: unknown;\n    selectedSort: ColumnState&#91;];\n    selectedPageIndex: number;\n}\n\ninterface AgGridState {\n    pages: Record\n    pageSize: number;\n}\n\ninterface AgGridAction {\n    type: string;\n    payload?: {\n        page?: string;\n        data?: unknown;\n    };\n}\n\ninterface AgGridContextType {\n    gridApiRef: React.MutableRefObject&lt;{ &#91;key: string]: { api: GridApi; } | null }&gt;;\n    state: AgGridState;\n    dispatch: React.Dispatch;\n    setGridApiRef: (page: string, params: { api: GridApi; }) =&gt; void;\n    resetGridApiRef: (page: string) =&gt; void;\n    gridReady: boolean,\n    setGridReady: (page: boolean) =&gt; void;\n}\n\n\/\/ Reducer Function\nconst agGridReducer = (state: AgGridState, action: AgGridAction): AgGridState =&gt; {\n    switch (action.type) {\n        case \"SET_PAGE_STATE\": {\n            const page = action.payload!.page!;\n            const data = action.payload!.data!;\n\n            return {\n                ...state,\n                pages: {\n                    ...state.pages,\n                    &#91;page]: {\n                        ...(state.pages&#91;page] || {}),\n                        ...data,\n                    },\n                },\n            };\n        }\n        case \"RESET_PAGE_STATE\": {\n            const page = action.payload!.page!;\n            const updatedPages = { ...state.pages };\n            delete updatedPages&#91;page];\n            \n            return {\n                ...state,\n                pages: updatedPages,\n            };\n        }\n        default:\n            return state;\n    }\n\n};\n\n\/\/ Create Context\nconst AgGridContext = createContext(undefined);\n\nexport const AgGridProvider: React.FC&lt;{ children: React.ReactNode }&gt; = ({ children }) =&gt; {\n\n    const &#91;state, dispatch] = useReducer(agGridReducer, { pageSize: 20, pages: {} });\n    const &#91;gridReady, setGridReady] = useState(false)\n\n    const gridApiRef = useRef&lt;{ &#91;key: string]: { api: GridApi } | null }&gt;({});\n\n    const setGridApiRef = (page: string, params: { api: GridApi }) =&gt; {\n        gridApiRef.current&#91;page] = params;\n        setGridReady(true)\n    };\n\n    const resetGridApiRef = useCallback((page: string) =&gt; {\n        if (gridApiRef.current&#91;page]) {\n            gridApiRef.current&#91;page] = null\n            setGridReady(false)\n        }\n    }, &#91;]);\n\n    return (\n        &lt;AgGridContext.Provider value={{ gridApiRef, state, dispatch, setGridApiRef, resetGridApiRef, gridReady, setGridReady }}&gt;\n            {children}\n        &lt;\/AgGridContext.Provider&gt;\n    );\n};\n\n\/\/ Hook for Consuming Context\nexport const useAgGridData = (): AgGridContextType =&gt; {\n    const context = useContext(AgGridContext);\n    if (!context) {\n        throw new Error(\"useAgGridData must be used within an AgGridProvider\");\n    }\n\n    return context;\n};\n\nexport const useAgGridData = () =&gt; useContext(AgGridContext);<\/mark>\n  <\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcca Table.tsx<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code><mark style=\"background-color:#fff\" class=\"has-inline-color has-black-color\">\n\/\/ Reusable AG Grid Table Component\n\nimport React, { useCallback, useEffect, useMemo } from \"react\";\nimport { AgGridReact } from \"ag-grid-react\";\nimport {\n    AllCommunityModule,\n    ColDef,\n    FilterChangedEvent,\n    FirstDataRenderedEvent,\n    GridReadyEvent,\n    ModuleRegistry,\n    PaginationChangedEvent,\n    RowDataUpdatedEvent,\n    SortChangedEvent,\n    themeBalham\n} from \"ag-grid-community\";\n\nimport { useAgGridData } from \"@\/context\";\nimport 'ag-grid-community\/styles\/ag-theme-alpine.css';\nimport 'ag-grid-community\/styles\/ag-theme-balham.css';\n\nModuleRegistry.registerModules(&#91;AllCommunityModule]);\n\ninterface TableProps {\n    rowData: unknown&#91;] | undefined;\n    columnDefs: ColDef&#91;];\n    page: string;\n    frameworkComponents?: unknown;\n    rowSelection?: \"singleRow\" | \"multiRow\";\n    rowSelectionMatchKey?: string;\n    suppressRowClickSelection?: boolean;\n    getRowStyle?: (params: unknown) =&gt; object;\n    isModalTable?: boolean;\n    loading?: boolean;\n}\n\nconst Table: React.FC = ({\n    columnDefs,\n    frameworkComponents,\n    page,\n    rowData,\n    rowSelection,\n    rowSelectionMatchKey = \"id\",\n    isModalTable = false,\n    loading = false,\n}) =&gt; {\n    const { gridApiRef, setGridApiRef, resetGridApiRef, state, dispatch } = useAgGridData() || {};\n    const { pageSize } = state || {};\n    const { selectedFilters, selectedSort, selectedPageIndex, selectedRows } = state?.pages&#91;page] || {};\n\n    const defaultColDef: ColDef = useMemo(\n        () =&gt; ({\n            flex: 1,\n            filter: \"agTextColumnFilter\",\n            menuTabs: &#91;\"filterMenuTab\"],\n        }),\n        &#91;]\n    );\n\n    \/\/ Store and restore filters\/sorting\/pagination on the mount.\n    const onFirstDataRendered = (params: FirstDataRenderedEvent) =&gt; {\n        if (selectedFilters) {\n            params.api.setFilterModel(selectedFilters);\n        }\n\n        setTimeout(() =&gt; {\n            if (pageSize) {\n                params.api.setGridOption(\"paginationPageSize\", pageSize);\n            }\n            if (selectedPageIndex) {\n                params.api.paginationGoToPage(selectedPageIndex);\n            }\n            if (selectedSort) {\n                params.api.applyColumnState({ state: selectedSort });\n            }\n        }, 0);\n    };\n\n    \/\/ Handle Row Selection Changes\n    const onSelectionChanged = useCallback(() =&gt; {\n        const selected = gridApiRef?.current?.&#91;page]?.api?.getSelectedRows() || &#91;];\n        dispatch({ type: \"SET_PAGE_STATE\", payload: { page, data: { selectedRows: selected } } });\n    }, &#91;gridApiRef, page, dispatch]);\n\n    \/\/ Capture Grid API\n    const onGridReady = (params: GridReadyEvent) =&gt; {\n        if (gridApiRef?.current &amp;&amp; setGridApiRef) {\n            setGridApiRef(page, params);\n        }\n    };\n\n    \/\/ Capture Pagination Changes\n    const onPageChanged = (params: PaginationChangedEvent) =&gt; {\n        if (params.newPage) {\n            const currentPage = gridApiRef?.current?.&#91;page]?.api?.paginationGetCurrentPage() || 0;\n            dispatch({ type: \"SET_PAGE_STATE\", payload: { page, data: { selectedPageIndex: currentPage } } });\n        }\n    };\n\n    const onFilterChanged = (params: FilterChangedEvent) =&gt; {\n        dispatch({ type: \"SET_PAGE_STATE\", payload: { page, data: { selectedFilters: params.api.getFilterModel() } } })\n    };\n\n    \/\/ Capture Sorting Changes\n    const onSortChanged = (params: SortChangedEvent) =&gt; {\n        dispatch({ type: \"SET_PAGE_STATE\", payload: { page, data: { selectedSort: params.api.getColumnState() } } });\n    };\n\n    \/\/ Restore Row Selection\n    const onRowDataUpdated = (params: RowDataUpdatedEvent) =&gt; {\n        if (selectedRows?.length) {\n            params.api.forEachNode((node) =&gt; {\n                const rData = node.data as Record;\n                const match = selectedRows.some((row) =&gt; {\n                    const selectedRow = row as Record;\n\n                    return selectedRow&#91;rowSelectionMatchKey] === rData&#91;rowSelectionMatchKey];\n                });\n                if (match) {\n                    node.setSelected(true, false);\n                }\n\n            });\n        }\n    };\n\n    const clearAllSelected = () =&gt; {\n        gridApiRef?.current?.&#91;page]?.api?.deselectAll();\n        dispatch({ type: \"SET_PAGE_STATE\", payload: { page, data: { selectedRows: &#91;] } } });\n    };\n\n    const modifiedColDef: ColDef&#91;] = useMemo(() =&gt; {\n        return columnDefs.map((col: ColDef) =&gt; {\n            return &#91;'Action', '#'].indexOf(col?.headerName || '') === -1 ? { \n                resizable: true, \n                sortable: true, \n                suppressMenu: true,\n                floatingFilter: true,\n                tooltipField: col.field,\n                ...col \n            } : col\n        })\n    }, &#91;columnDefs]);\n\n    useEffect(() =&gt; {\n        return () =&gt; {\n            if (resetGridApiRef &amp;&amp; !isModalTable) {\n                resetGridApiRef(page);\n            }\n        };\n    }, &#91;page, resetGridApiRef, isModalTable]);\n\n    return (\n        &lt;div style={{ width: \"100%\" }}&gt;\n\n            &lt;AgGridReact\n                domLayout=\"autoHeight\"\n                rowModelType=\"clientSide\"\n                theme={themeBalham}\n                rowData={rowData}\n                onRowDataUpdated={onRowDataUpdated}\n                onFirstDataRendered={onFirstDataRendered}\n                onGridReady={onGridReady}\n                onFilterChanged={onFilterChanged}\n                onSortChanged={onSortChanged}\n                onSelectionChanged={onSelectionChanged}\n                frameworkComponents={frameworkComponents}\n                pagination\n                paginationPageSize={pageSize}\n                onPaginationChanged={onPageChanged}\n                defaultColDef={defaultColDef}\n                columnDefs={modifiedColDef}\n                loading={loading}\n                {...(rowSelection &amp;&amp; { rowSelection: { \"mode\": rowSelection, selectAll: 'filtered' } })}\n            \/&gt;\n\n            {selectedRows?.length &gt; 0 &amp;&amp; (\n                &lt;div&gt;\n                    Selected {selectedRows.length} record(s).\n                    &lt;button onClick={clearAllSelected} className=\"underline text-blue-500\"&gt;Clear All&lt;\/button&gt;\n                &lt;\/div&gt;\n            )}\n        &lt;\/div&gt;\n    );\n};\n\nexport default Table;<\/mark>\n  <\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\ude80 How to Use<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code><mark style=\"background-color:#fff\" class=\"has-inline-color has-black-color\">\n&lt;AgGridProvider&gt;\n  &lt;Table\n    page=\"userList\"\n    rowData={users}\n    columnDefs={userColumns}\n    rowSelection=\"multiRow\"\n  \/&gt;\n&lt;\/AgGridProvider&gt;\n  <\/mark><\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">\u2705 Summary of Function Purposes<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>onGridReady<\/code>: Store API globally for reuse<\/li>\n\n\n\n<li><code>onFirstDataRendered<\/code>: Restore saved state<\/li>\n\n\n\n<li><code>onFilterChanged<\/code>: Persist filters<\/li>\n\n\n\n<li><code>onSortChanged<\/code>: Persist sort order<\/li>\n\n\n\n<li><code>onPageChanged<\/code>: Persist pagination<\/li>\n\n\n\n<li><code>onSelectionChanged<\/code>: Persist row selection<\/li>\n\n\n\n<li><code>onRowDataUpdated<\/code>: Reapply selections after data reload<\/li>\n\n\n\n<li><code>useEffect<\/code>: Cleanup when component unmounts<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udff7\ufe0f Why&nbsp;<code>page=\"userList\"<\/code>&nbsp;is Required<\/h2>\n\n\n\n<p>The&nbsp;<code>page<\/code>&nbsp;prop ensures that each instance of the table maintains its own unique state. Here\u2019s why it&#8217;s essential:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u2705 Unique State Keys:<\/strong>&nbsp;Each table uses the&nbsp;<code>page<\/code>&nbsp;value to store and retrieve filters, pagination, and sort order.<\/li>\n\n\n\n<li><strong>\u2705 Supports Multiple Tables:<\/strong>&nbsp;Prevents one table\u2019s state from affecting another\u2019s.<\/li>\n\n\n\n<li><strong>\u2705 Enables State Restoration:<\/strong>&nbsp;Helps reapply filters, sort, and selected rows when returning to a table.<\/li>\n\n\n\n<li><strong>\u2705 Safe Cleanup:<\/strong>&nbsp;Lets you clean up only the relevant table state when a component unmounts.<\/li>\n<\/ul>\n\n\n\n<p><strong>\ud83d\udd0e Example:<\/strong>&nbsp;What does state look like?<\/p>\n\n\n\n<div class=\"wp-block-group is-vertical is-content-justification-stretch is-layout-flex wp-container-core-group-is-layout-b16ad781 wp-block-group-is-layout-flex\">\n<pre class=\"wp-block-code\"><code><mark style=\"background-color:#fafafa\" class=\"has-inline-color has-black-color\">\n{\n  pages: {\n    userList: {\n      selectedFilters: { name: { type: \"contains\", filter: \"John\" } },\n      selectedSort: &#91; { colId: \"email\", sort: \"asc\" } ],\n      selectedPageIndex: 1,\n      selectedRows: &#91;...]\n    },\n    productList: {\n      selectedFilters: { category: { type: \"equals\", filter: \"Books\" } },\n      ...\n    }\n  }\n}<\/mark>\n    <\/code><\/pre>\n<\/div>\n\n\n\n<p>\ud83d\udd01 This setup ensures AG Grid state survives across page changes, modals, or tab switches\u2014great for UIs.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When creating enterprise-grade data tables using React, user experience and state management are critical. Making sure the grid retains filters, sorting, pagination, and chosen rows is a frequent problem, particularly when navigating between pages or components. We&#8217;ll demonstrate how to&#8230; <a class=\"more-link\" href=\"https:\/\/musikaar.com\/blog\/uncategorized\/creating-a-table-component-using-the-context-api\/\">Continue Reading &rarr;<\/a><\/p>\n","protected":false},"author":7,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[31,29],"class_list":["post-234","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-contextapi","tag-react"],"_links":{"self":[{"href":"https:\/\/musikaar.com\/blog\/wp-json\/wp\/v2\/posts\/234","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/musikaar.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/musikaar.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/musikaar.com\/blog\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/musikaar.com\/blog\/wp-json\/wp\/v2\/comments?post=234"}],"version-history":[{"count":7,"href":"https:\/\/musikaar.com\/blog\/wp-json\/wp\/v2\/posts\/234\/revisions"}],"predecessor-version":[{"id":242,"href":"https:\/\/musikaar.com\/blog\/wp-json\/wp\/v2\/posts\/234\/revisions\/242"}],"wp:attachment":[{"href":"https:\/\/musikaar.com\/blog\/wp-json\/wp\/v2\/media?parent=234"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/musikaar.com\/blog\/wp-json\/wp\/v2\/categories?post=234"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/musikaar.com\/blog\/wp-json\/wp\/v2\/tags?post=234"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}