Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add SWRDataTable filters to query params #19

Merged
merged 2 commits into from
Feb 9, 2024
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
6 changes: 5 additions & 1 deletion src/components/DataTable/DynamicActionComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ const ActionForm = ({ action, row }) => {
</AlertDialog>
)}
{!action?.trigger_confirmation && (
<Form action={action.url_path} method="post" {...form}>
<Form
action={action.url_path.replace("RESOURCE_ID", row.original.id)}
method="post"
{...form}
>
{action.method === "delete" && (
<input type="hidden" name="_method" value="delete" />
)}
Expand Down
62 changes: 59 additions & 3 deletions src/components/DataTable/SWRDataTable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-restricted-syntax */
import React, { useEffect } from "react";
import {
flexRender,
Expand All @@ -7,7 +8,7 @@ import {
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { useNavigate } from "react-router-dom";
import { useNavigate, useSearchParams } from "react-router-dom";
import useSWR from "swr";
import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons";

Expand All @@ -23,7 +24,11 @@ import {
} from "@/components/ui";
import { SectionTitle } from "@/components/SectionTitle";
import { formatDate, formatDateTime } from "@/lib/formatDate";
import { serializeQuery } from "@/lib/serializeQuery";
import {
serializeQuery,
serializeQueryObject,
deserializeQuery,
} from "@/lib/serializeQuery";
import { useTableState } from "./useTableState";
import { Link } from "@/components/Link";

Expand All @@ -42,6 +47,36 @@ function formatRequestParams(originalObj) {
};
}

const formatParamsToDataTable = (params, searchKey) => {
const { columnFilters = {}, pageIndex, pageSize, sorting } = params;

const sortingObj = sorting ? { sorting: [sorting] } : {};
const columnFiltersObj =
Object.keys(columnFilters).length > 0
? {
columnFilters: Object.entries(columnFilters).map(([id, value]) => ({
id,
value: Array.isArray(value)
? value
: id === searchKey
? value
: [value],
})),
}
: {};

const to = {
...sortingObj,
pagination: {
pageIndex: pageIndex || 0,
pageSize: !pageSize ? 10 : pageSize > 50 ? 50 : pageSize,
},
...columnFiltersObj,
};

return to;
};

const fetcher = async ([url, paramsObject]) => {
const formattedParams = formatRequestParams(paramsObject);
// @ts-expect-error TS(2554) FIXME: Expected 2 arguments, but got 1.
Expand Down Expand Up @@ -176,6 +211,15 @@ export function SWRDataTable({
setSelectedData?: (data: any[]) => void;
}) {
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
const initialSearch = {
...formatParamsToDataTable(
deserializeQuery(searchParams.toString()),
searchKey
),
...defaultParams,
};

const {
pagination,
rowSelection,
Expand All @@ -187,10 +231,22 @@ export function SWRDataTable({
setColumnVisibility,
setColumnFilters,
setSorting,
} = useTableState(defaultParams);
} = useTableState({ ...initialSearch });

const { pageIndex, pageSize } = pagination;

useEffect(() => {
const formattedParams = formatRequestParams({
columnFilters,
sorting,
pageIndex,
pageSize,
});

const params = serializeQueryObject(formattedParams);
setSearchParams(params);
}, [pageIndex, pageSize, sorting, columnFilters]);

const { data, error } = useSWR(
[fetchPath, { pageIndex, pageSize, sorting, columnFilters }],
fetcher,
Expand Down
95 changes: 93 additions & 2 deletions src/lib/serializeQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,78 @@
* serializeQuery(nestedParams);
* // Returns 'user[name]=John&user[age]=30'
*/

export function serializeQuery(params, prefix) {
const query = serializeParamsToQueryStrings(params, prefix);
return [...[].concat(...query)].join("&");
}

/**
* This function takes an object containing parameters and converts it into a query string.
* It follows the Rails pattern for nested query parameters.
* @example
* const params = {
"columnFilters": {
"is_general_thesis": [
"false"
]
},
"pageIndex": 0,
"pageSize": 10
};
* serializeQueryObject(params);
* // Returns {
"columnFilters[is_general_thesis][]": "false",
"pageIndex": "0",
"pageSize": "10"
* }
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great doc 👍

export function serializeQueryObject(params, prefix = undefined) {
const query = serializeParamsToQueryStrings(params, prefix);
return parseQueryStrings(query.filter((item) => item !== ""));
}

/**
* This function takes a query string and converts it into an object.
* It follows the Rails pattern for nested query parameters.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔝

* @example
* const nestedQueryString = 'user[name]=John&user[age]=30';
* deserializeQuery(nestedQueryString);
* // Returns { user: { name: 'John', age: 30 } }
*/
export function deserializeQuery(paramsString) {
const arr = decodeURIComponent(paramsString.replace(/\+/g, "%20")).split("&");
const result = {};

arr.forEach((item) => {
// eslint-disable-next-line prefer-const
let [path, value] = item.split("=");

// eslint-disable-next-line no-useless-escape
const pathParts = path.split(/[\[\]]/).filter((p) => p);
// eslint-disable-next-line array-callback-return, consistent-return
pathParts.reduce((acc, key, index) => {
if (index === pathParts.length - 1) {
if (key in acc && Array.isArray(acc[key])) {
acc[key].push(value);
} else if (key in acc) {
acc[key] = [acc[key], value];
} else {
acc[key] = value;
}
} else {
if (!(key in acc)) {
// eslint-disable-next-line no-restricted-globals
acc[key] = isNaN(Number(pathParts[index + 1])) ? {} : [];
}
return acc[key];
}
}, result);
});

return result;
}

function serializeParamsToQueryStrings(params, prefix = undefined) {
const query = Object.keys(params).map((key) => {
const value = params[key];

Expand All @@ -28,5 +98,26 @@ export function serializeQuery(params, prefix) {
return `${key}=${encodeURIComponent(value)}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be that the key also needs to be encoded?

});

return [...[].concat(...query)].join("&");
return query;
}

function parseQueryStrings(queryStrings) {
const result = {};
queryStrings.forEach((queryString) => {
const firstEqualIndex = queryString.indexOf("=");
const key = queryString.substring(0, firstEqualIndex);
const value = queryString.substring(firstEqualIndex + 1);

// Handle the case where the key already exists in the result object
if (result[key]) {
// If the key already exists and is not an array, convert it to an array
if (!Array.isArray(result[key])) {
result[key] = [result[key]];
}
result[key].push(decodeURIComponent(value));
} else {
result[key] = decodeURIComponent(value);
}
});
return result;
}