├── .gitignore
├── .prettierrc
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── package.json
├── src
└── index.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 | package-lock.json
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "printWidth": 200,
4 | "proseWrap": "always",
5 | "tabWidth": 4,
6 | "useTabs": false,
7 | "trailingComma": "none",
8 | "bracketSpacing": true,
9 | "semi": true
10 | }
11 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.defaultFormatter": "esbenp.prettier-vscode",
3 | "editor.bracketPairColorization.enabled": true,
4 | "editor.formatOnSave": true,
5 | "editor.formatOnPaste": true,
6 | "editor.wordWrap": "on",
7 | "git.ignoreLimitWarning": true
8 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Casey Bui
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | 
4 |
5 |
6 | # react-filter-by-url
7 |
8 |
9 |
10 |
11 | ### Try it now:
12 |
13 | [](https://codesandbox.io/s/elegant-keller-dpjp3e?file=/src/App.tsx)
14 |
15 | Live demo: https://react-filter-by-ulr-demo.vercel.app/list
16 |
17 |
18 | ## Problem
19 |
20 | This is the URL with query parameters in your browser address bar:
21 |
22 | ```
23 | https://example.com/search?page=2&type=public&status=open
24 | ```
25 |
26 | And you want to call an API with the exact same query parameters:
27 |
28 | ```
29 | https://example.com/api/search?page=2&type=public&status=open
30 | ```
31 |
32 | ### API url with query parameters is updated automatically by:
33 |
34 | - Paste in the link of the URL with query parameters to the address bar in browser
35 | - Change the URL query parameters directly in the browser
36 | - Update Filter Options in the UI
37 |
38 | ## useFilter
39 |
40 | ### Installation
41 |
42 | ```
43 | yarn add react-filter-by-url
44 | ```
45 |
46 | ### Implementation
47 |
48 | ```jsx
49 | import { useUrlFilter } from 'react-filter-by-url'
50 | ```
51 |
52 | ```jsx
53 | interface ListProps {}
54 |
55 | const DemoList: React.FC = ({}) => {
56 | // api url
57 | const apiUrl = 'https://rickandmortyapi.com/api/character'
58 |
59 | // list of params to filter
60 | const params = ['page', 'status']
61 |
62 | const { apiQuery, getDefaultParamValue, handleSelectFilter } = useUrlFilter(
63 | params,
64 | apiUrl
65 | )
66 |
67 | return <>
68 | ...
69 | >
70 | }
71 | ```
72 |
73 | #### You will need to pass in this hook:
74 |
75 | | Option | Description |
76 | | --------------- | --------------------------------------------------------------- |
77 | | `params` | An array of string, define all the query parameters the API has. |
78 | | `apiUrl` | A string, the base url of the API without the query parameters. |
79 | | `refreshParams` | (Optional) an Array of string, represents query params need to refresh to the defaul ones. Ex: refresh page=1 when there is a change in other query params |
80 |
81 |
82 | #### You will have access to the following values:
83 |
84 |
85 | | Option | Description |
86 | | --------------- | --------------------------------------------------------------- |
87 | | `apiQuery` | A string, API url with the query parameters (same query parameters with browser). |
88 | | `getDefaultParamValue` | A function, get the default value of a query param. |
89 | | `handleSelectFilter`| A function, handle the change in the filter options in the UI. |
90 | | `queryString`| A string, the recent query parameters. |
91 |
92 |
93 |
94 | Try toggling around the filters in the UI by using `handleSelectFilter` to update the URL query parameters:
95 |
96 | ```jsx
97 |
109 | ```
110 |
111 | Then you can simply call API every time the `apiQuery` changes which means URL query parameters change.
112 |
113 | ```jsx
114 | useEffect(() => {
115 | fetchApi()
116 | }, [apiQuery])
117 |
118 | const fetchApi = async () => {
119 | const response = await fetch(apiQuery)
120 | const data = await response.json()
121 | }
122 | ```
123 |
124 | Now you can try to change the URL query parameters in the browser or tinker around the filter UI and see the result.
125 |
126 | Live demo: https://react-filter-by-ulr-demo.vercel.app/list
127 |
128 |
129 | ## License
130 |
131 | MIT License
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-filter-by-url",
3 | "repository": {
4 | "type": "git",
5 | "url": "https://github.com/buikhacnam/react-filter-by-url.git"
6 | },
7 | "version": "0.3.0",
8 | "description": "A React library to update API Url with query parameters by looking at query parameters in the address bar of browser.",
9 | "main": "dist/index.js",
10 | "scripts": {
11 | "build": "rm -rf dist/ && prettier --write src/ && npm run build:esm",
12 | "build:esm": "tsc -p .",
13 | "build:cjs": "tsc --module CommonJS --outDir dist/cjs"
14 | },
15 | "author": "Casey Bui",
16 | "email": "buikhacnam11@gmail.com",
17 | "license": "MIT",
18 | "devDependencies": {
19 | "@types/node": "^17.0.21",
20 | "@types/react": "^17.0.37",
21 | "@types/react-dom": "^17.0.11",
22 | "react": "^17.0.2",
23 | "react-dom": "^17.0.2",
24 | "typescript": "^4.5.2"
25 | },
26 | "peerDependencies": {
27 | "react": "^17.0 || ^16.0 || ^18.0",
28 | "react-dom": "^17.0 || ^16.0 || 18.0"
29 | },
30 | "keywords": [
31 | "react",
32 | "url",
33 | "query",
34 | "query parameter",
35 | "query parameters",
36 | "filter",
37 | "filter by url",
38 | "url search",
39 | "state",
40 | "filter by url",
41 | "filter by url query",
42 | "filter by url query parameters",
43 | "filter by url query parameters state",
44 | "filter by url query parameters state react",
45 |
46 | "query by url",
47 | "query by url query",
48 | "query by url query parameters",
49 | "query by url query parameters state",
50 | "query by url query parameters state react",
51 |
52 | "search by url",
53 | "search by url query",
54 | "search by url query parameters",
55 | "search by url query parameters state",
56 | "search by url query parameters state react"
57 | ]
58 | }
59 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 |
3 | /**
4 | *
5 | * @Author Casey Bui
6 | * @Email buikhacnam11@gmail.com
7 | * @Github https://github.com/buikhacnam
8 | * @License MIT
9 | */
10 |
11 | export function useUrlFilter(params: string[], apiUrl: string, refreshParams?: string[]) {
12 | const url = new URL(window.location.href);
13 | const query = url.searchParams;
14 | const [apiQuery, setApiQuery] = useState(apiUrl);
15 | const [queryString, setQueryString] = useState('');
16 |
17 | useEffect(() => {
18 | const recentQuery = '?' + query.toString();
19 | setQueryString(recentQuery);
20 | setApiQuery(apiUrl + recentQuery);
21 |
22 | window.addEventListener('popstate', function (e) {
23 | window.location.reload();
24 | });
25 | }, []);
26 |
27 | const handleSelectFilter = (name: string, value: string): void => {
28 | const filter = convertParamsToFilterObject(params);
29 | filter[name] = value;
30 | if (refreshParams && refreshParams?.length > 0 && !refreshParams.includes(name)) {
31 | refreshParams.forEach((param) => {
32 | filter[param] = '';
33 | });
34 | }
35 | const query = buildQuery(params, filter);
36 | setApiQuery(apiUrl + query);
37 | setQueryString(query);
38 | window.history.pushState({}, '', query);
39 | };
40 |
41 | const buildQuery = (params: string[], filter: any): string => {
42 | let url = '?';
43 | params.forEach((param) => {
44 | url += `&${param}=${filter[param]}`;
45 | });
46 |
47 | return url.replace('&', '');
48 | };
49 |
50 | const convertParamsToFilterObject = (params: string[]): any => {
51 | const filter: any = {};
52 | params.forEach((param: string) => {
53 | filter[param] = query.get(param) || '';
54 | });
55 | return filter;
56 | };
57 |
58 | const getDefaultParamValue = (params: string, defaultValue: string): string => {
59 | return query.get(params) || defaultValue;
60 | };
61 |
62 | return {
63 | apiQuery,
64 | queryString,
65 | getDefaultParamValue,
66 | handleSelectFilter
67 | };
68 | }
69 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "declaration": true,
5 | "esModuleInterop": true,
6 | "jsx": "react",
7 | "lib": ["es5", "es2015", "es2016", "dom", "esnext"],
8 | "types": ["node"],
9 | "module": "es2015",
10 | "moduleResolution": "node",
11 | "noImplicitAny": true,
12 | "noUnusedLocals": true,
13 | "outDir": "dist",
14 | "sourceMap": true,
15 | "strict": true,
16 | "target": "es6",
17 | },
18 | "include": ["src"]
19 | }
20 |
--------------------------------------------------------------------------------