As the title says, lodash (_.debounce) implementation is not working for Select 2.0 async component.
It looks like the caching mechanism is caching the previous input and displaying that in the results.
Here is my forked code example:
Does anyone have a working debounce example that works for the 2.0 implementation of React Select Async?
Great reproduction!
It doesn't appear to me that this issue has to do with the cache.
It seems to me the issue is with an inaccurate expectation of how Lodash's debounce works.
Lodash specifies that
subsequent calls to the debounced function return the result of the last func invocation
Not that:
subsequent calls return promises which will resolve to the result of the next func invocation
This means each call which is within the wait period to our debounced loadOptions prop function is actually returning the last func invocation, and so the "real" promise we care about is never subscribed to.
Here is a forked example making it less react-select specific (removes the cacheOptions and defaultOptions props, passes the option {leading: true} to the debounce function, and makes the wait period longer):
https://codesandbox.io/s/olmjz7mn9z
User types a and waits past the debounce wait period (1 second):
Async#handleInputChange is called, with a new value of a.Async#loadOptions is called and subscribes to the promise returned from the passed in loadOptions fn (which is our _.debounced fn).User then types l, then a, then s, then k, then a (all within the debounce wait period). We now have an input showing Alaska, but with the dropdown showing both Alabama and Alaska. Here's why:
Async#handleInputChange is called, with a new value of al. Within this, Async#loadOptions is called and subscribes to the promise returned from the passed in loadOptions fn (which is our _.debounced fn), which is a promise which resolves to the correct data (both Alabama and Alaska).Async#handleInputChange is called, with a new value of ala. Within this, Async#loadOptions is called and subscribes to the promise returned from the passed in loadOptions fn (which is our _.debounced fn), which (because this event is within the wait period) returns the "result of the last func invocation", which is the promise from loadOptions from when al was typed in.alas, alask, and finally alaska each time with our debounced fn (because we're still within the debounce wait period) returning the "result of the last func invocation" which is the promise from loadOptions from when al was typed in. (This promise resolves to both Alabama and Alaska.)alaska was typed, and our debounced function now actually calls our true loadOptions function, which returns a promise resolving to the correct list of just Alaska. However, nothing is subscribed to this promise and it has no effect. Remember, when our word, alaska, was finished being typed in, our debounced loadOptions function still being within the wait period, returned the "result of the last func invocation" (the promise from loadOptions for the al) and the Async#loadOptions code, having received a fine value), used it and moved on.Using a promise-returning debounce method where subsequent calls return promises which will resolve to the result of the next func invocation.
Updated example using debounce-promise:
https://codesandbox.io/s/98vxxr18zw
@craigmichaelmartin Thanks for the clear explanation. Using { leading: true } does mean that the first character entered will always result in a call to loadOptions as shown in the timeline illustration here. So only the subsequent characters will be debounced
@craigmichaelmartin
Awesome job, this really works like a charm. I tried others solutions and this one it's perfect.
Well done! .
Another approach, that does seem to work with lodash's debounce, is to invoke the callback param React-Select pases to onLoad, and _don't_ return a Promise.
constructor(props) {
super(props);
const wait = 1000; // milliseconds
const loadOptions = (inputValue, callback) => {
this.getAsyncOptions(inputValue)
.then(results => callback(results))
// Explicitly not returning a Promise.
return;
}
this.debouncedLoadOptions = _.debounce(loadOptions, wait);
}
The tricky part is that it's easy to accidentally return a promise when you're calling an async search function, if you use an arrow function or an async/await function.
@craigmichaelmartin Worked perfectly for my use-case. Thanks for the detailed post and sandbox!
super(props); const wait = 1000; // milliseconds const loadOptions = (inputValue, callback) => { this.getAsyncOptions(inputValue) .then(results => callback(results)) // Explicitly not returning a Promise. return; } this.debouncedLoadOptions = _.debounce(loadOptions, wait); }
Using this solution, if you load options and change your input without selecting anything, it will make the second call but the subsequent results will not be handled or show up on the options list.
Using this solution, if you load options and change your input without selecting anything, it will make the second call but the subsequent results will not be handled or show up on the options list.
Huh, I'm not experiencing that problem in my project. Maybe there's some subtlety in my particular use-case that makes it work.
Well, if the technique I described doesn't work for you, then I suggest going with the approach craigmichaelmartin described, using debounce-promise.
We decided to use a combination of lodash/debounce and Creatable instead of AsyncCreatableSelect. This allows us to handle all the async stuff on my own component state instead of letting the Select handle it internally with all its quirks.
Hello guys, I'm using react hooks and I'm using an async redux action to get the data when the user starts searching. It works fine and all, but the debouncing doesn't work. I'm guessing because I'm doing an async action that returns a promise.
How can I solve this? Does anyone have this issue?
Hello guys, I'm using react hooks and I'm using an async redux action to get the data when the user starts searching. It works fine and all, but the debouncing doesn't work. I'm guessing because I'm doing an async action that returns a promise.
How can I solve this? Does anyone have this issue?
Try debounce-promise NPM package.
Hello -
In an effort to sustain the react-select project going forward, we're closing old issues.
We understand this might be inconvenient but in the best interest of supporting the broader community we have to direct our efforts towards the current major version.
If you aren't using the latest version of react-select please consider upgrading to see if it resolves any issues you're having.
However, if you feel this issue is still relevant and you'd like us to review it - please leave a comment and we'll do our best to get back to you!
Most helpful comment
Great reproduction!
It doesn't appear to me that this issue has to do with the cache.
The issue
It seems to me the issue is with an inaccurate expectation of how Lodash's debounce works.
Lodash specifies that
Not that:
This means each call which is within the wait period to our debounced
loadOptionsprop function is actually returning the last func invocation, and so the "real" promise we care about is never subscribed to.An example
Here is a forked example making it less react-select specific (removes the cacheOptions and defaultOptions props, passes the option {leading: true} to the debounce function, and makes the wait period longer):
https://codesandbox.io/s/olmjz7mn9z
User types
aand waits past the debounce wait period (1 second):Async#handleInputChangeis called, with a new value ofa.Async#loadOptionsis called and subscribes to the promise returned from the passed inloadOptionsfn (which is our _.debounced fn).User then types
l, thena, thens, thenk, thena(all within the debounce wait period). We now have an input showingAlaska, but with the dropdown showing both Alabama and Alaska. Here's why:Async#handleInputChangeis called, with a new value ofal. Within this,Async#loadOptionsis called and subscribes to the promise returned from the passed inloadOptionsfn (which is our _.debounced fn), which is a promise which resolves to the correct data (both Alabama and Alaska).Async#handleInputChangeis called, with a new value ofala. Within this,Async#loadOptionsis called and subscribes to the promise returned from the passed inloadOptionsfn (which is our _.debounced fn), which (because this event is within the wait period) returns the "result of the last func invocation", which is the promise fromloadOptionsfrom whenalwas typed in.alas,alask, and finallyalaskaeach time with our debounced fn (because we're still within the debounce wait period) returning the "result of the last func invocation" which is the promise fromloadOptionsfrom whenalwas typed in. (This promise resolves to both Alabama and Alaska.)alaskawas typed, and our debounced function now actually calls our trueloadOptionsfunction, which returns a promise resolving to the correct list of just Alaska. However, nothing is subscribed to this promise and it has no effect. Remember, when our word,alaska, was finished being typed in, our debouncedloadOptionsfunction still being within the wait period, returned the "result of the last func invocation" (the promise fromloadOptionsfor theal) and theAsync#loadOptionscode, having received a fine value), used it and moved on.A solution
Using a promise-returning debounce method where subsequent calls return promises which will resolve to the result of the next func invocation.
Updated example using
debounce-promise:https://codesandbox.io/s/98vxxr18zw