debounce-search-abstraction
Advanced tools
Comparing version
@@ -13,25 +13,37 @@ var __defProp = Object.defineProperty; | ||
__publicField(this, "debouncedQueryProcessor"); | ||
this.debouncedQueryProcessor = debounce((seq, query) => { | ||
if (seq === this.seq) { | ||
if (!query && typeof options.emptyQueryResult === "function") { | ||
options.resultsProcessor(options.emptyQueryResult()); | ||
} else { | ||
options.resultsProvider({ | ||
query, | ||
setResults: (result) => { | ||
if (seq === this.seq) { | ||
options.resultsProcessor(result); | ||
} | ||
} | ||
}); | ||
this.options = options; | ||
this.debouncedQueryProcessor = debounce( | ||
(seq, query) => { | ||
if (seq === this.seq) { | ||
this.lastQuery = query; | ||
if (!query) { | ||
this.options.resultsProcessor( | ||
this.options.emptyQueryResult(), | ||
query | ||
); | ||
} else { | ||
this.options.resultsProvider({ | ||
query, | ||
setResults: (result) => { | ||
if (seq === this.seq) { | ||
this.options.resultsProcessor( | ||
result, | ||
query | ||
); | ||
} | ||
}, | ||
isQueryStillActual: () => seq === this.seq | ||
}); | ||
} | ||
} | ||
} | ||
}, options.debounceMs || 300); | ||
}, | ||
this.options.debounceMs || 300 | ||
); | ||
} | ||
search(query, forceIfSame = false) { | ||
if (query !== this.lastQuery || forceIfSame) { | ||
this.lastQuery = query; | ||
const queryProcessed = this.options.trimQuery ? query.trim() : query; | ||
if (queryProcessed !== this.lastQuery || forceIfSame) { | ||
this.seq += 1; | ||
this.debouncedQueryProcessor(this.seq, query); | ||
if (!query) { | ||
this.debouncedQueryProcessor(this.seq, queryProcessed); | ||
if (!queryProcessed) { | ||
this.debouncedQueryProcessor.flush(); | ||
@@ -38,0 +50,0 @@ } |
@@ -1,1 +0,1 @@ | ||
(function(e,s){typeof exports=="object"&&typeof module!="undefined"?s(exports,require("lodash")):typeof define=="function"&&define.amd?define(["exports","lodash"],s):(e=typeof globalThis!="undefined"?globalThis:e||self,s(e.DebounceSearch={},e._))})(this,function(e,s){"use strict";var f=Object.defineProperty;var h=(e,s,r)=>s in e?f(e,s,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[s]=r;var o=(e,s,r)=>(h(e,typeof s!="symbol"?s+"":s,r),r);class r{constructor(u){o(this,"seq",0);o(this,"lastQuery","");o(this,"debouncedQueryProcessor");this.debouncedQueryProcessor=s.debounce((t,i)=>{t===this.seq&&(!i&&typeof u.emptyQueryResult=="function"?u.resultsProcessor(u.emptyQueryResult()):u.resultsProvider({query:i,setResults:d=>{t===this.seq&&u.resultsProcessor(d)}}))},u.debounceMs||300)}search(u,t=!1){(u!==this.lastQuery||t)&&(this.lastQuery=u,this.seq+=1,this.debouncedQueryProcessor(this.seq,u),u||this.debouncedQueryProcessor.flush())}}function n(c){return new r(c)}e.DebounceSearch=r,e.makeDebounceSearch=n,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}); | ||
(function(e,s){typeof exports=="object"&&typeof module!="undefined"?s(exports,require("lodash")):typeof define=="function"&&define.amd?define(["exports","lodash"],s):(e=typeof globalThis!="undefined"?globalThis:e||self,s(e.DebounceSearch={},e._))})(this,function(e,s){"use strict";var d=Object.defineProperty;var l=(e,s,o)=>s in e?d(e,s,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[s]=o;var u=(e,s,o)=>(l(e,typeof s!="symbol"?s+"":s,o),o);class o{constructor(r){u(this,"seq",0);u(this,"lastQuery","");u(this,"debouncedQueryProcessor");this.options=r,this.debouncedQueryProcessor=s.debounce((i,t)=>{i===this.seq&&(this.lastQuery=t,t?this.options.resultsProvider({query:t,setResults:h=>{i===this.seq&&this.options.resultsProcessor(h,t)},isQueryStillActual:()=>i===this.seq}):this.options.resultsProcessor(this.options.emptyQueryResult(),t))},this.options.debounceMs||300)}search(r,i=!1){const t=this.options.trimQuery?r.trim():r;(t!==this.lastQuery||i)&&(this.seq+=1,this.debouncedQueryProcessor(this.seq,t),t||this.debouncedQueryProcessor.flush())}}function c(n){return new o(n)}e.DebounceSearch=o,e.makeDebounceSearch=c,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}); |
@@ -1,7 +0,9 @@ | ||
export declare type SearchResultsProcessor<R> = (result: R) => void; | ||
export declare type SearchResultsProvider<R> = (options: { | ||
export type SearchResultsProcessor<R> = (result: R, query: string) => void; | ||
export type SetResultsCallback<R> = (result: R) => void; | ||
export type SearchResultsProvider<R> = (options: { | ||
query: string; | ||
setResults: SearchResultsProcessor<R>; | ||
setResults: SetResultsCallback<R>; | ||
isQueryStillActual: () => boolean; | ||
}) => void; | ||
export declare type DebounceSearchOptions<R> = { | ||
export type DebounceSearchOptions<R> = { | ||
debounceMs: number; | ||
@@ -11,2 +13,3 @@ resultsProvider: SearchResultsProvider<R>; | ||
emptyQueryResult: () => R; | ||
trimQuery?: boolean; | ||
}; | ||
@@ -18,2 +21,3 @@ export interface IDebounceSearch { | ||
export declare class DebounceSearch<R> implements IDebounceSearch { | ||
private options; | ||
private seq; | ||
@@ -20,0 +24,0 @@ private lastQuery; |
{ | ||
"name": "debounce-search-abstraction", | ||
"version": "3.0.2", | ||
"version": "3.1.0", | ||
"description": "A tool to easily make the search debounce properly", | ||
@@ -20,7 +20,7 @@ "keywords": [ | ||
"scripts": { | ||
"dev": "vite", | ||
"dev": "vite --config vite.demo.config.js", | ||
"build": "tsc && vite build", | ||
"preview": "vite preview", | ||
"test": "jest", | ||
"lint": "eslint --fix ." | ||
"lint": "eslint *.js lib/*", | ||
"lint:fix": "eslint --fix *.js lib/*" | ||
}, | ||
@@ -46,23 +46,24 @@ "files": [ | ||
"peerDependencies": { | ||
"lodash": "^4.17.21" | ||
"lodash": "^4" | ||
}, | ||
"dependencies": { | ||
"lodash": "^4.17.21" | ||
"lodash": "^4" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "^27.5.1", | ||
"@types/lodash": "^4.14.182", | ||
"@typescript-eslint/eslint-plugin": "^5.26.0", | ||
"@typescript-eslint/parser": "^5.26.0", | ||
"eslint": "^8.16.0", | ||
"@types/jest": "^29.5.3", | ||
"@types/lodash": "^4", | ||
"@typescript-eslint/eslint-plugin": "^5.32.0", | ||
"@typescript-eslint/parser": "^5.32.0", | ||
"eslint": "^8.21.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-plugin-editorconfig": "^3.2.0", | ||
"eslint-plugin-prettier": "^4.0.0", | ||
"jest": "^28.1.0", | ||
"prettier": "^2.6.2", | ||
"ts-jest": "^28.0.3", | ||
"typescript": "^4.7.2", | ||
"vite": "^2.9.9", | ||
"vite-plugin-dts": "^1.2.0" | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"jest": "^28.1.3", | ||
"prettier": "^2.7.1", | ||
"ts-jest": "^28.0.7", | ||
"typescript": "^4.7.4", | ||
"vite": "^2.9.14", | ||
"vite-plugin-dts": "^1.4.1", | ||
"vite-plugin-html": "^3.2.0" | ||
} | ||
} |
@@ -5,3 +5,3 @@ # debounce-search-abstraction | ||
ℹ️ it doesn't interact with DOM. It's abstract. You feed it with a search query, and it invokes your data callbacks. | ||
It encapsulates the debounce for your queries and skips the late results (concurrent requests). | ||
It encapsulates debounce for your queries and skips the late results (concurrent requests). | ||
@@ -20,6 +20,7 @@ ## Usage | ||
type SearchResult = { | ||
items: string[], | ||
transformedQuery: string; // in this guide we will be transforming the input query | ||
// usually you would want some array of resulting items here | ||
}; | ||
// make the instance (2 important callbacks) | ||
// create your instance (2 important callbacks) | ||
const search = new DebounceSearch<SearchResult>({ | ||
@@ -29,16 +30,32 @@ // input reaction debounce so API requests won't go out too often as you type | ||
// this guy makes the api request(s) to obtain and set (with callback) the search result data | ||
async resultsProvider({ query, setResults }) { | ||
await sleep(500); // emulating some api request here | ||
async resultsProvider({ query, setResults, isQueryStillActual }) { | ||
requestCallback(query); | ||
await sleep(200); // emulating some api request here | ||
const upperCased = query.toUpperCase(); // let's just do it uppercase | ||
// and set the results | ||
setResults({ | ||
items: query.split(''), // all the query chars, just for example | ||
transformedQuery: upperCased, | ||
}); | ||
// You may call this function as many times as you need, | ||
// in case you want to extend (replace) with some extra source results in series | ||
// Any set too late results will be ignored (if any input occur) | ||
// Notice, that any late results (if user updates the query) will be skipped automatically | ||
// So if you need any subsequent request(s) - make sure to check if our query is still actual | ||
if (isQueryStillActual()) { | ||
await sleep(200); // another backend request | ||
// btw, you don't need to check query actuality before calling setResult, | ||
// the check is already inside because it forced to skip outdated results. | ||
// This time, let's just add a query length | ||
// User would see the uppercase query first (setResults call above), | ||
// and then, after 200 ms the length information would be added. | ||
// In most real cases you would call it just once | ||
setResults({ | ||
// let's add a query length | ||
transformedQuery: `${upperCased} (${upperCased.length})`, | ||
}); | ||
} | ||
}, | ||
// and this guy applies the results (if they aren't outdated) | ||
resultsProcessor(result) { | ||
// note: it's called with the actual results only (late results won't get here) | ||
console.log('RESULT', result); // render some list here | ||
resultsProcessorCallback(result.transformedQuery); | ||
}, | ||
@@ -48,37 +65,34 @@ // when the query is empty - you don't have to make any requests, | ||
emptyQueryResult: () => ({ | ||
items: [], | ||
transformedQuery: '', | ||
}), | ||
}); | ||
// Example | ||
// We emulate the user typing "Hello Foobar" with some delays and then erasing the text | ||
// [ inputDelay, char ] | ||
const type: Array<[number, string]> = [ | ||
[100, 'h'], | ||
[300, 'e'], | ||
[400, 'l'], | ||
[200, 'l'], | ||
[400, 'o'], | ||
[1000, ' '], | ||
[400, 'F'], | ||
[400, 'o'], | ||
[300, 'o'], | ||
[100, 'b'], | ||
[200, 'a'], | ||
[100, 'r'], | ||
[1000, ''], // erasing the text | ||
// Example scenario (from the package test) | ||
// We emulate user typing "Hello There!" with some delays and then erasing the text back | ||
// array items description: | ||
// 1. input char | ||
// 2. waiting time before typing the next char | ||
// 3. outgoing query or null (if skipped due to debounce) | ||
// 4. search results or null (if skipped due to updated query) | ||
// we have a debounce set to 300 and API delays are 200 for uppercase result + 200 for length result | ||
const scenario: Array< | ||
[string, number, null | string, string[] | null] | ||
> = [ | ||
['H', 300, 'H', null], // no response - it takes 300+200=500 | ||
['e', 400, 'He', null], | ||
['l', 200, null, null], | ||
['l', 601, 'Hell', ['HELL']], // 500 is enough to get the first response | ||
['o', 1001, 'Hello', ['HELLO', 'HELLO (5)']], // 700 is enough to get both responses | ||
[' ', 250, null, null], // no request, quicker than debounce | ||
['T', 600, 'Hello T', ['HELLO T']], | ||
['h', 300, 'Hello Th', null], | ||
['e', 100, null, null], | ||
['r', 200, null, null], | ||
['e', 400, 'Hello There', null], | ||
['!', 800, 'Hello There!', ['HELLO THERE!', 'HELLO THERE! (12)']], | ||
['', 800, '', ['', ' (0)']], | ||
]; | ||
let text = ''; | ||
for (const [delay, char] of type) { | ||
await sleep(delay); | ||
if (char) { | ||
text += char; | ||
} else { | ||
text = ''; | ||
} | ||
search.search(text); | ||
} | ||
// in this example resultsProcessor will be called 3 times (due to input and request delays), | ||
// resultsProcessor will be called 3 times (due to input and request delays), | ||
// extra inputs invalidate late results. | ||
@@ -85,0 +99,0 @@ // Results of the following 3 queries will be given to resultsProcessor: |
Sorry, the diff of this file is not supported yet
10727
25.51%89
21.92%102
15.91%15
7.14%Updated