Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/components/table/DataTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from "react";
import PropTypes from "prop-types";
import classnames from "classnames";
import { Table } from "reactstrap";
// react-table doc: https://react-table-v7-docs.netlify.app/
import { useTable, useMountedLayoutEffect } from "react-table";
import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";

Expand All @@ -14,6 +15,9 @@ import {
} from "./utils";
import Paginator from "./Paginator";

/**
* Suitable when data is already available client side. Thus, pagination/filtering/sorting can be performed client side too.
*/
function DataTable({
config: userConfig,
onSelectedRowChange,
Expand Down
23 changes: 14 additions & 9 deletions src/components/table/queryParamsUtils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,51 @@ These funcitions helps to convert from obj to string and vice versa
*/

/**
* This function is used to convert filters from obj to string
* @argument {Array<Object>} filters
* @return {Object}
*/
const serializeFilterParams = (filters) =>
const serializeFilterParams = (filters) =>
filters.reduce(
// id and value connot be changed, are requested with this name in the lib
(acc, { id, value, }) => ({ ...acc, [id]: value, }),
{}
);

/**
* This function is used to convert filters from string to obj
* @argument {Object} filters
* @return {Array<Object>}
*/
const deserializeFilterParams = (filters) =>
Array.from(
Object.entries(filters).map(([filterName, filterValue]) => ({
id: filterName,
Object.entries(filters).map(([filterKey, filterValue]) => ({
id: filterKey,
value: filterValue,
}))
);

/**
* This function is used to convert ordering key from obj to string
* @argument {Array<Object>} sortBy
* @return {String}
*/
const serializeSortByParams = (sortBy) =>
sortBy
// id and desc connot be changed, are requested with this name in the lib
.map(({ id, desc, }) => `${desc ? "-" : ""}${id.split("-")[0]}`)
.join(",");

/**
* This function is used to convert ordering key from string to obj
* @argument {String} filters
* @return {Array<Object>}
*/
const deserializeSortByParams = (sortByStr) => ({
id: sortByStr.charAt(0) === "-" ? sortByStr.slice(1) : sortByStr,
desc: sortByStr.charAt(0) === "-",
});
const deserializeSortByParams = (sortByStr) =>
Array.from(
sortByStr.split(",").map((str) => ({
id: str.charAt(0) === "-" ? str.slice(1) : str,
desc: str.charAt(0) === "-",
}))
);

export {
serializeFilterParams,
Expand Down
24 changes: 13 additions & 11 deletions src/components/table/useDataTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,34 @@ import LoadingBoundary from "../containers/LoadingBoundary";
import DataTable from "./DataTable";
import useQueryParamsTable from "./useQueryParamsTable";

const pageParams = {
const defaultPageParams = {
page_size: 10,
page: 1,
};
const defaultModifier = (d) => d?.results || [];
const defaultModifier = (responseData) => responseData?.results || [];

/**
* Suitable for fetching data from a server. Thus, pagination/filtering/sorting in most cases should be handled by the server too.
* @param url URL to use to make the request.
* @param params Query parameters that will be added to the request, which can be overridden by table filters.
* @param toPassTableProps Table properties (ex. columns, config, initialState, SubComponent) that will be used for creating the table component.
* @param modifier Function that defines in which field of the response the data that will be used is located.
*/
function useDataTable(
{ url, params: defaultParams, initialParams, },
{ url, params: defaultParams, },
toPassTableProps,
modifier = defaultModifier
) {
/*
*/
const columnNames = toPassTableProps.columns.map(column => column.accessor);
// hook
const [params, tableInitialState, tableStateReducer] = useQueryParamsTable({
initialParams, columnNames,
});
const [params, tableInitialState, tableStateReducer] = useQueryParamsTable();

// state
const [initialLoading, setInitialLoading] = React.useState(true);

// API
const [{ data, loading, error, }, refetch] = useAxios({
url,
params: { ...pageParams, ...defaultParams, ...params, },
params: { ...defaultPageParams, ...defaultParams, ...params, },
});

// side-effects
Expand Down Expand Up @@ -74,7 +76,7 @@ function useDataTable(
]
);

return [data, tableNode, refetch, tableStateReducer, loading];
return [data, tableNode, refetch, tableStateReducer, loading, tableInitialState];
}

export default useDataTable;
45 changes: 17 additions & 28 deletions src/components/table/useQueryParamsTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,24 @@ import {
deserializeSortByParams
} from "./queryParamsUtils";

function useQueryParamsTable({ initialParams, columnNames, }) {
/**
This function automatically defines the params to be used in the request based on the filters set in the table and the URL search parameters.
*/
function useQueryParamsTable() {
// react-router
const navigate = useNavigate();
const location = useLocation();

// state
/*
initialParams is used for
fallback values incase URL query params are empty
*/
// Set the params based on url search params, if no parameter is set in the url it returns an empty dict
const [params, setParams] = React.useState(() => {
const urlParams = Object.fromEntries(
new URLSearchParams(location.search)
);
return Object.keys(urlParams).length ? urlParams : initialParams || {};
return Object.keys(urlParams).length ? urlParams : {};
});

// memo
const initialState = React.useMemo(() => {
// The table state changes every time the url params are modified, therefore every time some filters are modified
const tableInitialState = React.useMemo(() => {
const { ordering, page, ...filters } = params;
return {
pageIndex: page ? parseInt(page - 1, 10) : 0,
Expand All @@ -36,7 +35,7 @@ function useQueryParamsTable({ initialParams, columnNames, }) {
};
}, [params]);

// update query params to match table state
// Update query params to match table state and navigate to a page with the new params
React.useEffect(() => {
const search = `?${new URLSearchParams(params).toString()}`;
if (search !== location.search)
Expand All @@ -45,21 +44,11 @@ function useQueryParamsTable({ initialParams, columnNames, }) {

// callbacks
const onTableFilterDebounced = useAsyncDebounce(
/* this function MUST updates ONLY the parameters used by the table: the columns.
It has to maintain all the params used in the page outside the table.
*/
(filters) =>

setParams(({ ordering, ...others }) => ({ // we need to separate ordering, or this doesn't work
// Maintain the parameter
...Object.keys(others)
.filter(param => !!columnNames.includes(param.id)) // select only parameter external to the table
.reduce((savedParams, nextParam) => ({...savedParams, [nextParam[0]]: nextParam[1], }), {}), // concatenate them
// Add ordering parameter (if it's defined)
...(ordering ? { ordering, } : {}),
// add the filter parameters (the ones with columns names)
...serializeFilterParams(filters.filter(filter => columnNames.includes(filter.id))),
})),
(filters) =>
setParams(({ ordering, }) => ({
...(ordering ? { ordering, } : {}), // only include 'ordering' key if it defined
...serializeFilterParams(filters), // only serialize 'filters' field
})),
500
); // Debounce filter call for 500ms

Expand All @@ -68,9 +57,9 @@ function useQueryParamsTable({ initialParams, columnNames, }) {
sortBy?.length
? setParams(({ ordering, ...others }) => ({
...others,
ordering: serializeSortByParams(sortBy),
ordering: serializeSortByParams(sortBy), // override only 'ordering' key
}))
: setParams(({ ordering, ...others }) => others),
: setParams(({ ordering, ...others }) => others), // if sortBy has been reset remove the 'orgering' key
500
); // Debounce sortBy call for 500ms

Expand All @@ -97,7 +86,7 @@ function useQueryParamsTable({ initialParams, columnNames, }) {
[setParams, onTableFilterDebounced, onTableSortDebounced]
);

return [params, initialState, tableStateReducer];
return [params, tableInitialState, tableStateReducer];
}

export default useQueryParamsTable;