@perch33/react-usefilter-hook 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENCE.md +21 -0
- package/README.md +164 -0
- package/dist/cjs/index.js +51 -0
- package/dist/cjs/index.min.js +1 -0
- package/dist/esm/index.js +47 -0
- package/dist/esm/index.min.js +1 -0
- package/dist/types/index.d.ts +49 -0
- package/package.json +62 -0
package/LICENCE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Percy Saul Rondan Chuzon
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# @perch733/react-usefilter-hook
|
|
2
|
+
|
|
3
|
+
A lightweight and reusable React hook for filtering lists with accent normalization, punctuation removal, and TypeScript support.
|
|
4
|
+
Ideal for search bars, dynamic lists, admin dashboards, e-commerce filters, etc.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## π₯ Features
|
|
9
|
+
|
|
10
|
+
- π Removes accents (Γ‘ β a)
|
|
11
|
+
- βοΈ Removes punctuation and special characters
|
|
12
|
+
- π Case-insensitive search
|
|
13
|
+
- βοΈ Fully typed with TypeScript
|
|
14
|
+
- π Auto-updates when `data` changes
|
|
15
|
+
- π§ Accepts custom error component
|
|
16
|
+
- π§© Framework-agnostic, works in any React project
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## π¦ Installation
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install @perch733/react-usefilter-hook
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
or
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
yarn add @perch733/react-usefilter-hook
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## π Usage Example (Basic)
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { useFilter } from "@perch733/react-usefilter-hook";
|
|
37
|
+
|
|
38
|
+
const products = [
|
|
39
|
+
{ title: "CΓ‘mara FotogrΓ‘fica" },
|
|
40
|
+
{ title: "Microfono" },
|
|
41
|
+
{ title: "Cable HDMI" },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
export default function App() {
|
|
45
|
+
const { filterText, filteredData, handleFilterChange } = useFilter(
|
|
46
|
+
products,
|
|
47
|
+
"title",
|
|
48
|
+
<p>No results found</p>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div>
|
|
53
|
+
<input
|
|
54
|
+
type="search"
|
|
55
|
+
placeholder="Search..."
|
|
56
|
+
value={filterText}
|
|
57
|
+
onChange={handleFilterChange}
|
|
58
|
+
/>
|
|
59
|
+
|
|
60
|
+
<ul>
|
|
61
|
+
{filteredData.map((p, i) => (
|
|
62
|
+
<li key={i}>{p.title}</li>
|
|
63
|
+
))}
|
|
64
|
+
</ul>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## π― Advanced Example
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
const { filterText, filteredData, error, handleFilterChange } = useFilter(
|
|
74
|
+
users,
|
|
75
|
+
"name",
|
|
76
|
+
<div style={{ color: "red" }}>No users found</div>
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<>
|
|
81
|
+
<input
|
|
82
|
+
type="text"
|
|
83
|
+
placeholder="Search users"
|
|
84
|
+
value={filterText}
|
|
85
|
+
onChange={handleFilterChange}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
{filteredData.length === 0
|
|
89
|
+
? error
|
|
90
|
+
: filteredData.map((u) => <p key={u.id}>{u.name}</p>)}
|
|
91
|
+
</>
|
|
92
|
+
);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## π§ API Reference
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
useFilter<T>(data, key, errorComponent);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
| Param | Type | Description |
|
|
102
|
+
| ---------------- | ----------------- | --------------------------------- |
|
|
103
|
+
| `data` | `T[]` | Array of objects to filter |
|
|
104
|
+
| `key` | `keyof T` | Object field used for filtering |
|
|
105
|
+
| `errorComponent` | `React.ReactNode` | Element displayed when no matches |
|
|
106
|
+
|
|
107
|
+
## Returns
|
|
108
|
+
|
|
109
|
+
| Property | Type | Description |
|
|
110
|
+
| -------------------- | ------------------------------------ | ------------------------------------- |
|
|
111
|
+
| `filterText` | `string` | Current normalized search text |
|
|
112
|
+
| `filteredData` | `T[]` | List filtered according to user input |
|
|
113
|
+
| `error` | `React.ReactNode \| null` | Error component if no matches |
|
|
114
|
+
| `handleFilterChange` | `(e: ChangeEvent<HTMLInputElement>)` | Input handler |
|
|
115
|
+
|
|
116
|
+
### βοΈ How filtering works
|
|
117
|
+
|
|
118
|
+
- This hook automatically:
|
|
119
|
+
|
|
120
|
+
- Converts text to lowercase
|
|
121
|
+
|
|
122
|
+
- Removes accents (ÑéΓΓ³ΓΊ β aeiou)
|
|
123
|
+
|
|
124
|
+
- Removes punctuation and special characters
|
|
125
|
+
|
|
126
|
+
- Performs a normalized comparison
|
|
127
|
+
|
|
128
|
+
- Filters in real time as the user types
|
|
129
|
+
|
|
130
|
+
### π‘ When to use this hook?
|
|
131
|
+
|
|
132
|
+
- Product search inputs
|
|
133
|
+
|
|
134
|
+
- Admin panel filters
|
|
135
|
+
|
|
136
|
+
- Searchable dropdowns
|
|
137
|
+
|
|
138
|
+
- User lists
|
|
139
|
+
|
|
140
|
+
- Blog post search
|
|
141
|
+
|
|
142
|
+
- Table filtering
|
|
143
|
+
|
|
144
|
+
- Autocomplete components
|
|
145
|
+
|
|
146
|
+
## π Project Structure
|
|
147
|
+
|
|
148
|
+
Your installation will contain:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
dist/
|
|
153
|
+
ββ cjs/
|
|
154
|
+
ββ esm/
|
|
155
|
+
ββ types/
|
|
156
|
+
src/
|
|
157
|
+
README.md
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## π€ Author / Autor
|
|
161
|
+
|
|
162
|
+
**Percy Chuzon**
|
|
163
|
+
π§ [contacto@percychuzon.com](mailto:contacto@percychuzon.com)
|
|
164
|
+
π [https://wwww.percychuzon.com](https://www.percychuzon.com)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useFilter = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
/**
|
|
6
|
+
* π Normaliza texto para facilitar bΓΊsquedas:
|
|
7
|
+
* - Convierte a minΓΊsculas
|
|
8
|
+
* - Elimina tildes (Γ‘ β a)
|
|
9
|
+
* - Elimina signos de puntuaciΓ³n
|
|
10
|
+
* - Quita espacios sobrantes
|
|
11
|
+
*/
|
|
12
|
+
/** π Elimina tildes, signos, y pasa todo a minΓΊsculas */
|
|
13
|
+
function normalizarTexto(texto) {
|
|
14
|
+
return texto
|
|
15
|
+
.normalize("NFD") // separa letras con tildes en componentes
|
|
16
|
+
.replace(/[\u0300-\u036f]/g, "") // elimina los diacrΓticos (tildes)
|
|
17
|
+
.replace(/[.,/#!$%^&*;:{}=\-_`~()ΒΏ?Β‘!]/g, "") // elimina signos
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.trim();
|
|
20
|
+
}
|
|
21
|
+
const useFilter = (data, key, errorComponent) => {
|
|
22
|
+
const [filterText, setFilterText] = (0, react_1.useState)("");
|
|
23
|
+
const [filteredData, setFilteredData] = (0, react_1.useState)(data);
|
|
24
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
25
|
+
/** Maneja el texto que escribe el usuario en el input */
|
|
26
|
+
const handleFilterChange = (e) => {
|
|
27
|
+
const searchText = normalizarTexto(e.target.value);
|
|
28
|
+
setFilterText(searchText);
|
|
29
|
+
};
|
|
30
|
+
/** Filtra la lista cada vez que cambia el texto o los datos */
|
|
31
|
+
(0, react_1.useEffect)(() => {
|
|
32
|
+
const filteredItems = data.filter((item) => {
|
|
33
|
+
const valorCampo = String(item[key]);
|
|
34
|
+
return normalizarTexto(valorCampo).includes(filterText);
|
|
35
|
+
});
|
|
36
|
+
setFilteredData(filteredItems);
|
|
37
|
+
if (filteredItems.length === 0 && filterText !== "") {
|
|
38
|
+
setError(errorComponent);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
setError(null);
|
|
42
|
+
}
|
|
43
|
+
}, [data, filterText, key]);
|
|
44
|
+
return {
|
|
45
|
+
filterText,
|
|
46
|
+
filteredData,
|
|
47
|
+
error,
|
|
48
|
+
handleFilterChange,
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
exports.useFilter = useFilter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.useFilter=void 0;const react_1=require("react");function normalizarTexto(e){return e.normalize("NFD").replace(/[\u0300-\u036f]/g,"").replace(/[.,/#!$%^&*;:{}=\-_`~()ΒΏ?Β‘!]/g,"").toLowerCase().trim()}const useFilter=(e,t,r)=>{const[a,l]=(0,react_1.useState)(""),[o,n]=(0,react_1.useState)(e),[s,u]=(0,react_1.useState)(null);return(0,react_1.useEffect)(()=>{const l=e.filter(e=>normalizarTexto(String(e[t])).includes(a));n(l),0===l.length&&""!==a?u(r):u(null)},[e,a,t]),{filterText:a,filteredData:o,error:s,handleFilterChange:e=>{const t=normalizarTexto(e.target.value);l(t)}}};exports.useFilter=useFilter;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* π Normaliza texto para facilitar bΓΊsquedas:
|
|
4
|
+
* - Convierte a minΓΊsculas
|
|
5
|
+
* - Elimina tildes (Γ‘ β a)
|
|
6
|
+
* - Elimina signos de puntuaciΓ³n
|
|
7
|
+
* - Quita espacios sobrantes
|
|
8
|
+
*/
|
|
9
|
+
/** π Elimina tildes, signos, y pasa todo a minΓΊsculas */
|
|
10
|
+
function normalizarTexto(texto) {
|
|
11
|
+
return texto
|
|
12
|
+
.normalize("NFD") // separa letras con tildes en componentes
|
|
13
|
+
.replace(/[\u0300-\u036f]/g, "") // elimina los diacrΓticos (tildes)
|
|
14
|
+
.replace(/[.,/#!$%^&*;:{}=\-_`~()ΒΏ?Β‘!]/g, "") // elimina signos
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.trim();
|
|
17
|
+
}
|
|
18
|
+
export const useFilter = (data, key, errorComponent) => {
|
|
19
|
+
const [filterText, setFilterText] = useState("");
|
|
20
|
+
const [filteredData, setFilteredData] = useState(data);
|
|
21
|
+
const [error, setError] = useState(null);
|
|
22
|
+
/** Maneja el texto que escribe el usuario en el input */
|
|
23
|
+
const handleFilterChange = (e) => {
|
|
24
|
+
const searchText = normalizarTexto(e.target.value);
|
|
25
|
+
setFilterText(searchText);
|
|
26
|
+
};
|
|
27
|
+
/** Filtra la lista cada vez que cambia el texto o los datos */
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const filteredItems = data.filter((item) => {
|
|
30
|
+
const valorCampo = String(item[key]);
|
|
31
|
+
return normalizarTexto(valorCampo).includes(filterText);
|
|
32
|
+
});
|
|
33
|
+
setFilteredData(filteredItems);
|
|
34
|
+
if (filteredItems.length === 0 && filterText !== "") {
|
|
35
|
+
setError(errorComponent);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
setError(null);
|
|
39
|
+
}
|
|
40
|
+
}, [data, filterText, key]);
|
|
41
|
+
return {
|
|
42
|
+
filterText,
|
|
43
|
+
filteredData,
|
|
44
|
+
error,
|
|
45
|
+
handleFilterChange,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useState,useEffect}from"react";function normalizarTexto(e){return e.normalize("NFD").replace(/[\u0300-\u036f]/g,"").replace(/[.,/#!$%^&*;:{}=\-_`~()ΒΏ?Β‘!]/g,"").toLowerCase().trim()}export const useFilter=(e,t,r)=>{const[a,l]=useState(""),[n,o]=useState(e),[u,i]=useState(null);return useEffect(()=>{const l=e.filter(e=>normalizarTexto(String(e[t])).includes(a));o(l),0===l.length&&""!==a?i(r):i(null)},[e,a,t]),{filterText:a,filteredData:n,error:u,handleFilterChange:e=>{const t=normalizarTexto(e.target.value);l(t)}}};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ChangeEvent } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
4
|
+
* π useFilter β Hook reutilizable para filtrar listas de datos
|
|
5
|
+
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
6
|
+
*
|
|
7
|
+
* β Permite filtrar un array (data) segΓΊn un campo especΓfico (key)
|
|
8
|
+
* β Devuelve:
|
|
9
|
+
* - filterText β texto escrito por el usuario
|
|
10
|
+
* - filteredData β lista filtrada
|
|
11
|
+
* - error β componente mostrado cuando no hay resultados
|
|
12
|
+
* - handleFilterChange β manejador para input de bΓΊsqueda
|
|
13
|
+
*
|
|
14
|
+
* @template T Tipo genΓ©rico del array a filtrar
|
|
15
|
+
*
|
|
16
|
+
* @param {T[]} data
|
|
17
|
+
* Lista completa de objetos que se desea filtrar
|
|
18
|
+
*
|
|
19
|
+
* @param {keyof T} key
|
|
20
|
+
* Propiedad del objeto que se usarΓ‘ para filtrar (ej: "title")
|
|
21
|
+
*
|
|
22
|
+
* @param {React.ReactNode} errorComponent
|
|
23
|
+
* Componente mostrado si no se encuentran resultados
|
|
24
|
+
*
|
|
25
|
+
* @returns {{
|
|
26
|
+
* filterText: string,
|
|
27
|
+
* filteredData: T[],
|
|
28
|
+
* error: React.ReactNode,
|
|
29
|
+
* handleFilterChange: (e: ChangeEvent<HTMLInputElement>) => void
|
|
30
|
+
* }}
|
|
31
|
+
*
|
|
32
|
+
* @Error
|
|
33
|
+
* Se activa cuando:
|
|
34
|
+
* - El usuario escribe algo
|
|
35
|
+
* - No existen resultados coincidentes
|
|
36
|
+
*
|
|
37
|
+
* Puedes usarlo para mostrar:
|
|
38
|
+
* <p>No se encontrΓ³ nada</p>
|
|
39
|
+
*
|
|
40
|
+
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
41
|
+
*/
|
|
42
|
+
type UseFilterReturn<T> = {
|
|
43
|
+
filterText: string;
|
|
44
|
+
filteredData: T[];
|
|
45
|
+
error: React.ReactNode;
|
|
46
|
+
handleFilterChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
|
47
|
+
};
|
|
48
|
+
export declare const useFilter: <T>(data: T[], key: keyof T, errorComponent: React.ReactNode) => UseFilterReturn<T>;
|
|
49
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@perch33/react-usefilter-hook",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight and reusable React hook for filtering lists with accent normalization, punctuation removal and TypeScript support.",
|
|
5
|
+
"main": "dist/cjs/index.min.js",
|
|
6
|
+
"module": "dist/esm/index.min.js",
|
|
7
|
+
"types": "dist/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/esm/index.min.js",
|
|
11
|
+
"require": "./dist/cjs/index.min.js",
|
|
12
|
+
"types": "./dist/types/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/cjs",
|
|
17
|
+
"dist/esm",
|
|
18
|
+
"dist/types"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"scripts": {
|
|
22
|
+
"clean": "rimraf dist",
|
|
23
|
+
"build": "npm run clean && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json && terser dist/cjs/index.js -o dist/cjs/index.min.js --compress --mangle && terser dist/esm/index.js -o dist/esm/index.min.js --compress --mangle"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"react",
|
|
27
|
+
"hook",
|
|
28
|
+
"filter",
|
|
29
|
+
"usefilter",
|
|
30
|
+
"search",
|
|
31
|
+
"list filter",
|
|
32
|
+
"normalization",
|
|
33
|
+
"typescript",
|
|
34
|
+
"frontend",
|
|
35
|
+
"react hook",
|
|
36
|
+
"filter hook"
|
|
37
|
+
],
|
|
38
|
+
"author": "Percy Chuzon <contacto@percychuzon.com>",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"homepage": "https://react-usefilter-hook.percychuzon.com/",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/perch733/perch733-react-usefilter-hook.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/perch733/perch733-react-usefilter-hook/issues"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"react": ">=17.0.0",
|
|
53
|
+
"react-dom": ">=17.0.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/react": "^18.3.21",
|
|
57
|
+
"@types/react-dom": "^18.3.7",
|
|
58
|
+
"rimraf": "^6.0.1",
|
|
59
|
+
"typescript": "^5.8.3",
|
|
60
|
+
"terser": "^5.39.2"
|
|
61
|
+
}
|
|
62
|
+
}
|