Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
37 changes: 27 additions & 10 deletions src/components/table/queryParamsUtils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,63 @@ These funcitions helps to convert from obj to string and vice versa
*/

/**
* @argument {String} id
* @return {String}
*/
const deserializeKeyId = (id) =>
// eslint-disable-next-line no-nested-ternary
id.indexOf("weight") >= 0
? id.indexOf("__gte") >= 0
? id.replace("__gte", "-gte")
: `${id}-gte`
: id;

/**
* 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, }),
(acc, { id, value, }) => ({ ...acc, [`${id.replace("-", "__")}`]: 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: deserializeKeyId(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: deserializeKeyId(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 defualtPageParams = {
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: { ...defualtPageParams, ...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;