Reputation: 1965
I have this zustand store:
useStore.js:
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
export const useStore = create(
immer(set => ({
table: {
columns: [],
data: [],
globalFilter: '',
pagination: {},
setColumns: columns => set(s => { s.table.columns = columns }),
setData: data => set(s => { s.table.data = data }),
updateCell: (r, c, val) => set(s => { s.table.data[r][c] = val }),
addRow: () => set(s => {
const emptyRow = {};
s.table.columns.forEach(c => emptyRow[c.accessorKey] = '');
s.table.data.splice(
(s.table.pagination.pageIndex + 1) * s.table.pagination.pageSize - 1, 0, emptyRow
)
}),
setGlobalFilter: fs => set(s => { s.table.globalFilter = fs }),
setPagination: p => set(s => { s.table.pagination = p }),
}
}))
)
which I am trying to use with the tanstack table:
new.js:
import TABLE from '../../work.json';
import { useEffect, useState } from "react"
import * as XLSX from 'xlsx/xlsx.mjs';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUpDown } from '@fortawesome/free-solid-svg-icons';
import { useStore } from '../../hooks/useStore';
import {
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable
} from '@tanstack/react-table';
function NikitaDev() {
const {
columns,
data,
globalFilter,
pagination,
setColumns,
setData,
addRow,
setGlobalFilter,
setPagination
} = useStore(s => s.table);
useEffect(() => {
setColumns(TABLE.headers.map(h => ({
accessorKey: h,
header: h,
cell: EditableCell
})));
setData(TABLE.data);
setPagination({
pageIndex: 0,
pageSize: 14
});
}, [])
const table = useReactTable({
data,
columns,
state: {
columnOrder: ["Stavební_díl-ID", "Allright_Stavebni_dil_ID"],
globalFilter,
pagination
},
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
columnResizeMode: 'onChange',
onPaginationChange: setPagination,
})
return (
<div className="flex flex-col gap-[1rem] m-[1rem]">
<div className="flex">
<input
value={globalFilter}
onChange={e => setGlobalFilter(e.target.value)}
placeholder='Search All columns'
className='flex-1 focus:outline-none'
/>
<button
onClick={() => {
const workbook = XLSX.utils.book_new();
const worksheet = XLSX.utils.json_to_sheet(data);
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet 1');
XLSX.writeFile(workbook, 'data.xlsx');
}}
className='bg-green-500 rounded text-white px-4 py-2'
>
Export to excel
</button>
</div>
<table className="w-full text-left text-sm text-gray-500">
<thead className="text-xs text-gray-700 uppercase bg-gray-50">
{table.getHeaderGroups().map(hg => (
<tr key={hg.id}>
{hg.headers.map(h => (
<th key={h.id} className='relative border px-6 py-3'>
{h.column.columnDef.header}
<button onClick={h.column.getToggleSortingHandler()}>
<span className='ml-[0.5rem]'>
{{
asc: " 🔼",
desc: " 🔽",
}[h.column.getIsSorted()] ||
<FontAwesomeIcon icon={faUpDown} />
}
</span>
</button>
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(r => (
<tr key={r.id}>
{r.getVisibleCells().map(c => (
<td key={c.id} className='border p-[0.25rem]'>
{flexRender(c.column.columnDef.cell, c.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
<span className='text-sm'>
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
</span>
<div className="flex gap-[0.5rem]">
<button
disabled={!table.getCanPreviousPage()}
onClick={() => table.previousPage()}
className='px-[0.5rem] py-[0.25rem] border border-gray-300 rounded'
>
<
</button>
<button
disabled={!table.getCanNextPage()}
onClick={() => table.nextPage()}
className='px-[0.5rem] py-[0.25rem] border border-gray-300 rounded'
>
>
</button>
<select
value={table.getState().pagination.pageSize}
onChange={e => {
table.setPageSize(Number(e.target.value))
}}
className='px-[0.5rem] py-[0.25rem] border border-gray-300 rounded'
>
{[10, 20, 30, 40, 50, 100, 200, 500].map(pageSize => (
<option key={pageSize} value={pageSize}>
{pageSize}
</option>
))}
</select>
<button
onClick={() => addRow()} // Call the addRow function when button is clicked
className='bg-blue-500 rounded text-white px-4 py-2 ml-2'
>
Add Row
</button>
</div>
</div>
)
}
function EditableCell({ getValue, row, column }) {
const updateCell = useStore(s => s.table.updateCell);
const initalValue = getValue();
const [value, setValue] = useState(initalValue);
useEffect(() => {
setValue(initalValue);
}, [initalValue])
function onChange(value) {
setValue(value);
updateCell(row.index, column.id, value);
}
return (
<input
type='text'
value={value}
onChange={e => onChange(e.target.value)}
className='w-full px-3 py-2'
/>
);
}
export default NikitaDev;
previously, I was using useState, to store it locally, but need to have access to data and columns in other components as well. problem. But now, it does not render anything. Here is the version without zustand that works:
old.js:
import TABLE from '../../work.json';
import { useEffect, useState } from "react"
import * as XLSX from 'xlsx/xlsx.mjs';
import {
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable
} from '@tanstack/react-table';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUpDown } from '@fortawesome/free-solid-svg-icons';
function NikitaDev() {
const [columns, setColumns] = useState(() => {
return TABLE.headers.map(h => ({
accessorKey: h,
header: h,
cell: EditableCell
}));
});
const [data, setData] = useState(TABLE.data);
const [globalFilter, setGlobalFilter] = useState('');
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 14
});
const table = useReactTable({
data,
columns,
state: {
columnOrder: ["Stavební_díl-ID", "Allright_Stavebni_dil_ID"],
globalFilter,
pagination
},
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
columnResizeMode: 'onChange',
onPaginationChange: setPagination,
meta: {
updateData: (rowIndex, columnId, value) => setData(
prev => prev.map((row, index) => index === rowIndex ? {
...prev[index],
[columnId]: value
} : row)
),
addRow: () => {
const newData = [...data];
const emptyRow = {};
columns.forEach(col => {
emptyRow[col.accessorKey] = '';
});
newData.splice((pagination.pageIndex + 1) * pagination.pageSize - 1, 0, emptyRow); // Insert at the end of current page
setData(newData);
}
}
})
return (
<div className="flex flex-col gap-[1rem] m-[1rem]">
<div className="flex">
<input
value={globalFilter}
onChange={e => setGlobalFilter(e.target.value)}
placeholder='Search All columns'
className='flex-1 focus:outline-none'
/>
<button
onClick={() => {
const workbook = XLSX.utils.book_new();
const worksheet = XLSX.utils.json_to_sheet(data);
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet 1');
XLSX.writeFile(workbook, 'data.xlsx');
}}
className='bg-green-500 rounded text-white px-4 py-2'
>
Export to excel
</button>
</div>
<table className="w-full text-left text-sm text-gray-500">
<thead className="text-xs text-gray-700 uppercase bg-gray-50">
{table.getHeaderGroups().map(hg => (
<tr key={hg.id}>
{hg.headers.map(h => (
<th key={h.id} className='relative border px-6 py-3'>
{h.column.columnDef.header}
<button onClick={h.column.getToggleSortingHandler()}>
<span className='ml-[0.5rem]'>
{{
asc: " 🔼",
desc: " 🔽",
}[h.column.getIsSorted()] ||
<FontAwesomeIcon icon={faUpDown} />
}
</span>
</button>
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(r => (
<tr key={r.id}>
{r.getVisibleCells().map(c => (
<td key={c.id} className='border p-[0.25rem]'>
{flexRender(c.column.columnDef.cell, c.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
<span className='text-sm'>
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
</span>
<div className="flex gap-[0.5rem]">
<button
disabled={!table.getCanPreviousPage()}
onClick={() => table.previousPage()}
className='px-[0.5rem] py-[0.25rem] border border-gray-300 rounded'
>
<
</button>
<button
disabled={!table.getCanNextPage()}
onClick={() => table.nextPage()}
className='px-[0.5rem] py-[0.25rem] border border-gray-300 rounded'
>
>
</button>
<select
value={table.getState().pagination.pageSize}
onChange={e => {
table.setPageSize(Number(e.target.value))
}}
className='px-[0.5rem] py-[0.25rem] border border-gray-300 rounded'
>
{[10, 20, 30, 40, 50, 100, 200, 500].map(pageSize => (
<option key={pageSize} value={pageSize}>
{pageSize}
</option>
))}
</select>
<button
onClick={() => table.options.meta.addRow()} // Call the addRow function when button is clicked
className='bg-blue-500 rounded text-white px-4 py-2 ml-2'
>
Add Row
</button>
</div>
</div>
)
}
function EditableCell({ getValue, row, column, table }) {
const initalValue = getValue();
const [value, setValue] = useState(initalValue);
useEffect(() => {
setValue(initalValue);
}, [initalValue])
function onChange(value) {
setValue(value);
table.options.meta.updateData(row.index, column.id, value);
}
return (
<input
type='text'
value={value}
onChange={e => onChange(e.target.value)}
className='w-full px-3 py-2'
/>
)
}
export default NikitaDev;
Upvotes: 1
Views: 1071
Reputation: 66
Tanstack react-table expects a function for controlling the change handlers which accept both a value and an updater as its parameter (think of React's useState api).
The setters you're providing from your store, only accept regular values and not updater functions too.
For a regular use-case you can do something like this for e.g. the pagination:
state: {
pagination,
},
onPaginationChange: (updater) => {
const newValue =
updater instanceof Function ? updater(pagination) : updater;
setSelected(newValue);
},
See the relevant documentation here: https://tanstack.com/table/latest/docs/framework/react/guide/table-state#2-updaters-can-either-be-raw-values-or-callback-functions
Upvotes: 2