2024-07-16
Mastering React Query and React Table: A Comprehensive Guide with Examples
Table of Contents:
- Introduction
- React Query
2.1 Core Concepts
2.2 Queries
2.3 Mutations
2.4 Query Invalidation
2.5 Prefetching
2.6 Pagination
2.7 Infinite Queries
2.8 Optimistic Updates
2.9 Devtools - React Table
3.1 Core Concepts
3.2 Basic Table
3.3 Sorting
3.4 Filtering
3.5 Pagination
3.6 Row Selection
3.7 Column Ordering
3.8 Grouping
3.9 Expanding - Conclusion
- Introduction
React Query and React Table are powerful libraries that simplify data fetching, state management, and table rendering in React applications. This guide will walk you through the main features of both libraries with practical examples.
- React Query
React Query is a data-fetching and state management library for React applications. It provides hooks for fetching, caching, and updating data in your React applications.
2.1 Core Concepts
The main hooks in React Query are:
- useQuery: For fetching data
- useMutation: For modifying data
- useQueryClient: For accessing the QueryClient instance
2.2 Queries
Queries are used to fetch data from an API. Here's a basic example:
import { useQuery } from 'react-query';
function Users() {
const { isLoading, error, data } = useQuery('users', fetchUsers);
if (isLoading) return 'Loading...';
if (error) return 'An error has occurred: ' + error.message;
return (
<ul>
{data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// Fetch function
const fetchUsers = async () => {
const res = await fetch('https://api.example.com/users');
return res.json();
};
2.3 Mutations
Mutations are used to create/update/delete data. Here's an example:
import { useMutation, useQueryClient } from 'react-query';
function AddUser() {
const queryClient = useQueryClient();
const mutation = useMutation(newUser => {
return fetch('https://api.example.com/users', {
method: 'POST',
body: JSON.stringify(newUser),
});
}, {
onSuccess: () => {
queryClient.invalidateQueries('users');
},
});
return (
<form onSubmit={(e) => {
e.preventDefault();
mutation.mutate({ name: e.target.name.value });
}}>
<input type="text" name="name" />
<button type="submit">Add User</button>
</form>
);
}
2.4 Query Invalidation
Query invalidation is used to mark queries as stale and refetch them. Here's how to invalidate queries:
const queryClient = useQueryClient();
// Invalidate every query in the cache
queryClient.invalidateQueries();
// Invalidate every query with a key that starts with `todos`
queryClient.invalidateQueries('todos');
2.5 Prefetching
Prefetching can be used to fetch data before it's needed:
const queryClient = useQueryClient();
queryClient.prefetchQuery('users', fetchUsers);
2.6 Pagination
React Query makes pagination straightforward:
function Users() {
const [page, setPage] = useState(1);
const { isLoading, error, data } = useQuery(['users', page], () => fetchUsers(page), {
keepPreviousData: true,
});
// ... render logic
}
2.7 Infinite Queries
For infinite scrolling, use the useInfiniteQuery
hook:
function InfiniteUsers() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery('infiniteUsers', fetchUsers, {
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
});
// ... render logic
}
2.8 Optimistic Updates
Optimistic updates can improve perceived performance:
const queryClient = useQueryClient();
const mutation = useMutation(updateUser, {
onMutate: async newUser => {
await queryClient.cancelQueries('user');
const previousUser = queryClient.getQueryData('user');
queryClient.setQueryData('user', newUser);
return { previousUser };
},
onError: (err, newUser, context) => {
queryClient.setQueryData('user', context.previousUser);
},
onSettled: () => {
queryClient.invalidateQueries('user');
},
});
2.9 Devtools
React Query comes with built-in devtools:
import { ReactQueryDevtools } from 'react-query/devtools';
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* The rest of your app */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
- React Table
React Table is a lightweight, extensible datagrids for React.
3.1 Core Concepts
The main hooks in React Table are:
- useTable: The core hook
- useSortBy: For sorting
- useFilters: For filtering
- usePagination: For pagination
- useRowSelect: For row selection
- useColumnOrder: For column ordering
- useGroupBy: For grouping
- useExpanded: For expanding rows
3.2 Basic Table
Here's a basic example of a table:
import { useTable } from 'react-table';
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({ columns, data });
return (
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => (
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
))}
</tr>
);
})}
</tbody>
</table>
);
}
3.3 Sorting
To add sorting functionality:
import { useTable, useSortBy } from 'react-table';
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({ columns, data }, useSortBy);
return (
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render('Header')}
<span>
{column.isSorted
? column.isSortedDesc
? ' 🔽'
: ' 🔼'
: ''}
</span>
</th>
))}
</tr>
))}
</thead>
{/* ... tbody */}
</table>
);
}
3.4 Filtering
To add filtering:
import { useTable, useFilters } from 'react-table';
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
state,
setFilter,
} = useTable({ columns, data }, useFilters);
return (
<>
<input
value={state.filters[0]?.value || ''}
onChange={e => setFilter('columnName', e.target.value)}
placeholder={'Search...'}
/>
<table {...getTableProps()}>
{/* ... table content */}
</table>
</>
);
}
3.5 Pagination
To add pagination:
import { useTable, usePagination } from 'react-table';
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
page,
nextPage,
previousPage,
canNextPage,
canPreviousPage,
pageOptions,
state: { pageIndex, pageSize },
prepareRow,
} = useTable({ columns, data }, usePagination);
return (
<>
<table {...getTableProps()}>
{/* ... table content using 'page' instead of 'rows' */}
</table>
<div>
<button onClick={() => previousPage()} disabled={!canPreviousPage}>
Previous
</button>
<button onClick={() => nextPage()} disabled={!canNextPage}>
Next
</button>
<div>
Page {pageIndex + 1} of {pageOptions.length}
</div>
</div>
</>
);
}
3.6 Row Selection
To add row selection:
import { useTable, useRowSelect } from 'react-table';
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
selectedFlatRows,
} = useTable({ columns, data }, useRowSelect, hooks => {
hooks.visibleColumns.push(columns => [
{
id: 'selection',
Header: ({ getToggleAllRowsSelectedProps }) => (
<input type="checkbox" {...getToggleAllRowsSelectedProps()} />
),
Cell: ({ row }) => (
<input type="checkbox" {...row.getToggleRowSelectedProps()} />
),
},
...columns,
]);
});
return (
<>
<table {...getTableProps()}>
{/* ... table content */}
</table>
<pre>
<code>
{JSON.stringify(
{
selectedRows: selectedFlatRows.map(row => row.original),
},
null,
2
)}
</code>
</pre>
</>
);
}
3.7 Column Ordering
To allow column reordering:
import { useTable, useColumnOrder } from 'react-table';
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
setColumnOrder,
} = useTable({ columns, data }, useColumnOrder);
const changeOrder = () => {
setColumnOrder(['id', 'name', 'age']); // example order
};
return (
<>
<button onClick={changeOrder}>Change Column Order</button>
<table {...getTableProps()}>
{/* ... table content */}
</table>
</>
);
}
3.8 Grouping
To add grouping functionality:
import { useTable, useGroupBy, useExpanded } from 'react-table';
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
} = useTable({ columns, data }, useGroupBy, useExpanded);
return (
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>
{column.canGroupBy ? (
<span {...column.getGroupByToggleProps()}>
{column.isGrouped ? '🛑 ' : '👊 '}
</span>
) : null}
{column.render('Header')}
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => (
<td {...cell.getCellProps()}>
{cell.isGrouped ? (
<>
<span {...row.getToggleRowExpandedProps()}>
{row.isExpanded ? '👇' : '👉'}
</span>{' '}
{cell.render('Cell')} ({row.subRows.length})
</>
) : cell.isAggregated ? (
cell.render('Aggregated')
) : cell.isPlaceholder ? null : (
cell.render('Cell')
)}
</td>
))}
</tr>
);
})}
</tbody>
</table>
);
}
3.9 Expanding
To add row expansion:
import { useTable, useExpanded } from 'react-table';
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
state: { expanded },
} = useTable({ columns, data }, useExpanded);
return (
<table {...getTableProps()}>
{/* ... thead */}
<tbody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row);
return (
<>
<tr {...row.getRowProps()}>
{row.cells.map(cell => (
<td {...cell.getCellProps()}>
{cell.render('Cell')}
{cell.column.id === 'expander' ? (
<span {...row.getToggleRowExpandedProps()}>
{row.isExpanded ? '👇' : '👉'}
</span>
) : null}
</td>
))}
</tr>
{row.isExpanded && (
<tr>
<td colSpan={columns.length}>
{/* Additional content for expanded row */}
</td>
</tr>
)}
</>
);
})}
</tbody>
</table>
);
}
- Conclusion
React Query and React Table are powerful libraries that can significantly simplify data management and table rendering in React applications. React Query provides an intuitive way to handle server state, while React Table offers a flexible and feature-rich solution for building complex tables.
By leveraging these libraries, you can create more efficient, maintainable, and user-friendly React applications. Remember to consult the official documentation for the most up-to-date information and advanced usage scenarios.