Skip to content

Instantly share code, notes, and snippets.

@gengue
Last active October 29, 2021 22:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gengue/20dc3a80d808ee908380488ce673542c to your computer and use it in GitHub Desktop.
Save gengue/20dc3a80d808ee908380488ce673542c to your computer and use it in GitHub Desktop.
React hook to filter a list using local state and Fuse.js Demo: https://codesandbox.io/s/tender-stallman-qi1gl?fontsize=14
import React, { useState } from "react";
import ReactDOM from "react-dom";
import useFuzzySearch from "./useFuzzySearch";
const persons = [
{ id: 1, name: "genesis" },
{ id: 2, name: "jose" },
{ id: 3, name: "mauro" },
{ id: 4, name: "fredo" },
{ id: 5, name: "meggie" },
{ id: 6, name: "gustavo 527" },
{ id: 7, name: "santiago" },
];
function FuzzySearchDemo() {
const { list, onSearch } = useFuzzySearch(persons, ["name"]);
return (
<div className="App">
<h1>Fuzzy Search</h1>
<input name="search_fuzzy" onChange={onSearch} />
<ul>
{list.map(i => (
<li key={i.id}>{i.name}</li>
))}
</ul>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<FuzzySearchDemo />, rootElement);
import { useState, useEffect } from 'react';
function useDebounce(value, delay) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(
() => {
// Set debouncedValue to value (passed in) after the specified delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Return a cleanup function that will be called every time ...
// ... useEffect is re-called. useEffect will only be re-called ...
// ... if value changes (see the inputs array below).
// This is how we prevent debouncedValue from changing if value is ...
// ... changed within the delay period. Timeout gets cleared and restarted.
// To put it in context, if the user is typing within our app's ...
// ... search box, we don't want the debouncedValue to update until ...
// ... they've stopped typing for more than 500ms.
return () => {
clearTimeout(handler);
};
},
// Only re-call effect if value changes
// You could also add the "delay" var to inputs array if you ...
// ... need to be able to change that dynamically.
[value, delay]
);
return debouncedValue;
}
export default useDebounce;
import { useEffect, useState, useMemo, useRef } from 'react';
import useDebounce from './useDebounce';
import Fuse from 'fuse.js';
/**
* @const
*/
const DEFAULT_FUSE_OPTIONS = {
shouldSort: true,
location: 0,
distance: 100,
threshold: 0.6,
};
/* fuse instance */
let fuse;
/**
* useFuzzySearch
* @example
* const { list, onSearch } = useFuzzySearch(apiList, [ 'first_name', 'last_name' ]);
* @param {array} originalList=[]
* @param {array} keys=[]
* @param {number} ms=500
* @returns {object} = list<Array>, onSearch<function>
*/
function useFuzzySearch(originalList = [], keys = [], ms = 500) {
const source = useRef(originalList);
const [list, setList] = useState(originalList);
const [rawSearch, setSearch] = useState(null);
// the hook will only return the latest value (what we passed in)
const search = useDebounce(rawSearch, ms);
const fuseOptions = useMemo(() => ({ ...DEFAULT_FUSE_OPTIONS, keys }), [
keys,
]);
const onSearch = e => {
const value = e.target ? e.target.value : e.q;
setSearch(value);
};
useEffect(
() => {
if (!source.current || source.current.length === 0) {
setList(originalList);
}
fuse = new Fuse(originalList, fuseOptions);
source.current = originalList;
},
[fuseOptions, originalList]
);
useEffect(
() => {
function handleSearch(text) {
// null means we have to show the original list, not the filtered one
let result = null;
if (text && text !== '') {
result = fuse.search(text);
}
setList(result === null ? source.current : result);
}
if (search !== null) {
handleSearch(search);
}
},
[search]
);
return { list, onSearch };
}
export default useFuzzySearch;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment