Skip to content

Instantly share code, notes, and snippets.

@luizcieslak
Last active June 16, 2020 14:03
Show Gist options
  • Save luizcieslak/3426858cc837eae36495177b5efab986 to your computer and use it in GitHub Desktop.
Save luizcieslak/3426858cc837eae36495177b5efab986 to your computer and use it in GitHub Desktop.
useFuseSearch + useDebounce (TS and JS version) https://codesandbox.io/s/usefusesearch-ts-h8ueb
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 { useState, useEffect } from "react";
function useDebounce(value: string, delay: number) {
// 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);
};
}, // You could also add the "delay" var to inputs array if you ... // Only re-call effect if value changes
// ... need to be able to change that dynamically.
[value, delay]);
return debouncedValue;
}
export default useDebounce;
//equals to https://gist.github.com/gengue/20dc3a80d808ee908380488ce673542c
// w/ caseSensitive option
import { useEffect, useState, useMemo, useRef } from 'react'
import useDebounce from './useDebounce'
import Fuse from 'fuse.js'
/**
* @const
*/
const DEFAULT_FUSE_OPTIONS = {
shouldSort: false,
location: 0,
distance: 100,
threshold: 0.6,
caseSensitive: false
}
/* fuse instance */
let fuse
/**
* useFuseSearch
* @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 useFuseSearch(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 = value => {
// 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 { results: list, onSearch }
}
export default useFuseSearch
//based on https://gist.github.com/gengue/20dc3a80d808ee908380488ce673542c
import { useEffect, useState, useMemo, useRef } from "react";
import useDebounce from "./useDebounce";
import Fuse, { FuseResult } from "fuse.js";
/**
* @const
* @see: https://fusejs.io/api/options.html
*/
const DEFAULT_FUSE_OPTIONS = {
shouldSort: false,
location: 0,
distance: 100,
threshold: 0.2,
caseSensitive: false
};
/**
* useFuseSearch
* @example
* const { list, onSearch } = useFuseSearch(apiList, [ 'first_name', 'last_name' ]);
* @param {array} originalList=[]
* @param {array} keys=[]
* @param {number} ms=500
* @returns {object} = results<Array>, onSearch<function>
*/
function useFuseSearch<T, K extends keyof T>(
originalList: T[] = [],
keys: K[] = [],
ms = 600
) {
// Comment the following line if you want to return an empty list when there is no results.
const source = useRef(originalList);
const [list, setList] = useState(originalList);
const [rawSearch, setSearch] = useState("");
// 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 as string[] }),
[keys]
);
/* fuse instance */
let fuse = useMemo(() => new Fuse(originalList, fuseOptions), [
originalList,
fuseOptions
]);
/**
* onSearch function to be added directly to input element.
* @example <input onChange={onSearch} />
* @param e React.ChangeEvent<HTMLInputElement>
*
* change this if you want to pass a string to useFuseSearch
*/
const onSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value);
};
useEffect(() => {
source.current = originalList;
}, [originalList]);
useEffect(() => {
function handleSearch(text: any) {
let result: FuseResult<T>[] = [];
if (text && text !== "") {
result = fuse.search(text);
}
// if results is empty, return original list
// remove this if you want to return a message for empty results.
setList(result.length === 0 ? source.current : result);
}
if (search !== null) {
handleSearch(search);
}
// Warning: puttingg fuse in array of deps gives an infinite loop.
}, [search, rawSearch]);
return { results: list, onSearch };
}
export default useFuseSearch;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment