├── .prettierrc ├── nodemon.json ├── tsconfig.build.json ├── src ├── index.spec.ts └── index.ts ├── .travis.yml ├── tslint.json ├── .gitignore ├── .npmignore ├── tsconfig.json ├── package.json └── README.md /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["lib"], 3 | "ext": "js", 4 | "exec": "node lib/index" 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "tests", "lib", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | const hello = () => 1; 2 | 3 | describe('hello world', () => { 4 | it('should greet', () => { 5 | expect(hello()).toEqual(1); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - v10.16.0 4 | script: 5 | - npm run lint 6 | - npm run test 7 | cache: 8 | - npm 9 | after_success: 10 | - bash <(curl -s https://codecov.io/bash) 11 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false 16 | }, 17 | "rulesDirectory": [] 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled output 2 | /dist 3 | /node_modules 4 | /lib 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json 36 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | npm-debug.log* 3 | 4 | # Coverage directory used by tools like istanbul 5 | coverage 6 | .nyc_output 7 | 8 | # Dependency directories 9 | node_modules 10 | 11 | # npm package lock 12 | package-lock.json 13 | yarn.lock 14 | 15 | # Project files 16 | src 17 | test 18 | examples 19 | .travis.yml 20 | .babelrc 21 | .gitignore 22 | 23 | # TODO: Remove .npmignore and use files property of package.json instead 24 | lib/*.map 25 | lib/*.tsbuildinfo 26 | .prettierrc 27 | nodemon.json 28 | tsconfig.build.json 29 | tsconfig.json 30 | tslint.json 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "incremental": true, 5 | "outDir": "lib", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "lib": ["es6", "dom", "es2016", "es2017"], 9 | "sourceMap": true, 10 | "allowJs": false, 11 | "jsx": "react", 12 | "declaration": true, 13 | "moduleResolution": "node", 14 | "forceConsistentCasingInFileNames": true, 15 | "noImplicitReturns": true, 16 | "noImplicitThis": true, 17 | "noImplicitAny": true, 18 | "strictNullChecks": true, 19 | "suppressImplicitAnyIndexErrors": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "esModuleInterop": true 23 | }, 24 | "exclude": ["node_modules", "lib"] 25 | } 26 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Fuse, { FuseOptions } from 'fuse.js'; 2 | import { useMemo, useState } from 'react'; 3 | 4 | export interface IFuzzyClient { 5 | keyword: string; 6 | result: T[]; 7 | resetSearch: () => void; 8 | search: (keyword: string) => void; 9 | } 10 | 11 | export function useFuzzy( 12 | data: T[], 13 | options: FuseOptions, 14 | ): IFuzzyClient { 15 | const [keyword, setKeyword] = useState(''); 16 | const resetSearch = () => setKeyword(''); 17 | 18 | const searcher = useMemo(() => { 19 | const defaultOptions = { tokenize: true, threshold: 0.2 }; 20 | return new Fuse(data, { ...defaultOptions, ...options }); 21 | }, [data, options]); 22 | const result: T[] = keyword ? (searcher.search(keyword) as T[]) : data; 23 | 24 | return { 25 | keyword, 26 | resetSearch, 27 | result, 28 | search: setKeyword, 29 | }; 30 | } 31 | 32 | export const useSearch = useFuzzy; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-use-fuzzy", 3 | "version": "1.0.4", 4 | "description": "React hook for client side fuzzy search using Fuse.js", 5 | "homepage": "https://github.com/hellojoshuatonga/react-use-fuzzy#readme", 6 | "main": "lib/index.js", 7 | "types": "lib/index.d.ts", 8 | "scripts": { 9 | "prepublish": "npm run build", 10 | "build": "tsc -p tsconfig.build.json", 11 | "format": "prettier --write \"src/**/*.ts\"", 12 | "dev": "concurrently --handle-input \"wait-on lib/index.js && nodemon\" \"tsc -w -p tsconfig.build.json\" ", 13 | "test": "jest", 14 | "test:watch": "jest --watch", 15 | "test:cov": "jest --coverage", 16 | "lint": "tslint -p tsconfig.json -c tslint.json" 17 | }, 18 | "jest": { 19 | "preset": "ts-jest", 20 | "testEnvironment": "node" 21 | }, 22 | "keywords": [ 23 | "react", 24 | "hooks", 25 | "fuzzy", 26 | "fuzzy-search", 27 | "fuse.js", 28 | "client-search" 29 | ], 30 | "author": "Joshua Tonga (https://hellojoshuatonga.github.io/)", 31 | "license": "MIT", 32 | "peerDependencies": { 33 | "react": ">= 16", 34 | "fuse.js": "^3.4.5" 35 | }, 36 | "devDependencies": { 37 | "@types/jest": "^24.0.23", 38 | "@types/react": "^16.9.15", 39 | "concurrently": "^5.0.0", 40 | "jest": "^24.9.0", 41 | "nodemon": "^2.0.1", 42 | "prettier": "^1.19.1", 43 | "ts-jest": "^24.2.0", 44 | "ts-node": "^8.5.4", 45 | "tslint": "^5.20.1", 46 | "typescript": "^3.7.3", 47 | "wait-on": "^3.3.0", 48 | "fuse.js": "^3.4.5", 49 | "react": "^16.12.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-use-fuzzy 2 | 3 | [![NPM version](https://img.shields.io/npm/v/react-use-fuzzy.svg?style=flat-square)](https://npmjs.org/package/react-use-fuzzy) 4 | [![Build Status](https://img.shields.io/travis/hellojoshuatonga/react-use-fuzzy/master.svg?style=flat-square)](https://travis-ci.org/hellojoshuatonga/react-use-fuzzy) 5 | [![Coverage Status](https://img.shields.io/codecov/c/github/hellojoshuatonga/react-use-fuzzy/master.svg?style=flat-square)](https://codecov.io/gh/hellojoshuatonga/react-use-fuzzy/branch/master) 6 | 7 | A react hook in Typescript for client side fuzzy search using [Fuse.js](https://github.com/krisk/fuse). 8 | 9 | ## 🚀 Install 10 | ```bash 11 | $ npm install --save react-use-fuzzy 12 | ``` 13 | 14 | ## 🎈 Usage 15 | ```typescript 16 | import React, { useState } from 'react'; 17 | import { useFuzzy } from 'react-use-fuzzy'; 18 | import './App.css'; 19 | 20 | interface Product { 21 | id: number; 22 | name: string; 23 | } 24 | 25 | interface ProductItemProps { 26 | product: Product; 27 | } 28 | 29 | const ProductItem: React.FC = ({ product }) => { 30 | return
  • {product.name}
  • ; 31 | } 32 | 33 | interface ProductsListProps { 34 | products: Product[]; 35 | } 36 | 37 | const ProductsList: React.FC = ({ products }) => { 38 | return ( 39 |
      40 | { 41 | products.map((product) => ( 42 | 43 | )) 44 | } 45 |
    46 | ) 47 | } 48 | 49 | const App: React.FC = () => { 50 | const productsData: Product[] = [ 51 | { 52 | id: 1, 53 | name: 'T-shirt', 54 | }, 55 | { 56 | id: 2, 57 | name: 'Short', 58 | }, 59 | ] 60 | const [products, setProducts] = useState(productsData); 61 | 62 | const { result, keyword, search } = useFuzzy(products, { 63 | keys: ['name'], 64 | }); 65 | 66 | return ( 67 |
    68 |
    69 | search(e.target.value)} 74 | /> 75 | 76 |
    77 |
    78 | ); 79 | } 80 | 81 | export default App; 82 | ``` 83 | 84 | ## ⛓️ Peer Dependencies 85 | - [React](https://www.npmjs.com/package/react) (16.x.x) 86 | - [Fuse.js](https://www.npmjs.com/package/fuse.js/v/3.4.3) (3.x.x) 87 | 88 | ## 🎉 Acknowledgements 89 | - Inspired by [react-use-fuse](https://github.com/MartinL83/react-use-fuse) Fuse.js wrapper. 90 | 91 | 92 | ## License 93 | MIT © 94 | --------------------------------------------------------------------------------