├── .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 | ![npm](https://img.shields.io/npm/v/react-filter-by-url) 2 | ![npm lisence](https://img.shields.io/npm/l/react-filter-by-url) 3 | ![npm type definitions](https://img.shields.io/npm/types/react-moralis) 4 | 5 | 6 | # react-filter-by-url 7 | 8 | 9 | 10 | 11 | ### Try it now: 12 | 13 | [![Edit bold-ellis-6rg1t](https://codesandbox.io/static/img/play-codesandbox.svg)](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 | --------------------------------------------------------------------------------