react-autocomplete icon indicating copy to clipboard operation
react-autocomplete copied to clipboard

Poor render performance on lists of >500 items

Open carlpaten-ivadolabs opened this issue 9 years ago • 9 comments

I took the code from the static-data example and stuck it into an internal LOB application. The list of items contains ~700 entries. The first few keystrokes take about a second to register each, less and less as branches are culled out of the render tree.

One solution would be to add a props that sets a maximum limit of elements to render. Nobody wants to browse through a 700 element list, so all of that rendering effort is not going into good use anyway.

My work-around: having a local variable in render() that tracks the amount of items rendered. When it gets high enough, shouldItemRender returns false unconditionally.

let rendered = 0;
const maxRendered = 20;

let shouldItemRender = (branch, state) => {
    if (rendered < maxRendered) {
        let shouldRender = branch.Name.toUpperCase().indexOf(this.state.value.toUpperCase()) !== -1;
        if (shouldRender) {
            rendered += 1;
        }
        return shouldRender;
    } else {
        return false;
    }
}

EDIT: the above work-around actually isn't one, it breaks everything else. See #443.

carlpaten-ivadolabs avatar Jul 19 '16 17:07 carlpaten-ivadolabs

The easiest solution is probably to cut/slice items before passing it to Autocomplete. You can control items entirely from the outside (i.e. sorting, filtering, etc) and specify that every item passed in should render (shouldItemRender={() => true}). Unless, of course, there's a technical reason why you couldn't create a shallow copy of the list.

CMTegner avatar Jul 19 '16 17:07 CMTegner

@CMTegner: but then, if filtering should be done on the items list before it is passed to Autocomplete, what is the point of exposing shouldItemRender?

carlpaten-ivadolabs avatar Jul 19 '16 17:07 carlpaten-ivadolabs

if filtering should be done on the items list before it is passed to Autocomplete, what is the point of exposing shouldItemRender?

Yeah, you're sort of hitting an edge case usage-wise here. The current implementation was never meant to handle hundreds of items. Overriding shouldItemRender is simply an escape hatch which lets you disable some of Autocomplete's internal workings.

It's not unlikely that we'll move away from doing the filter and sort operations internally in the future, so this approach may end up being the way to use Autocomplete in the future.

On a related note: If you're doing filter/sort operations on large collections in the browser you should perhaps consider moving the heavy lifting to a ServiceWorker to prevent blocking the UI thread.

CMTegner avatar Jul 25 '16 17:07 CMTegner

@CMTegner Could we perhaps add a limitItemsLength prop to the component so that only the top x items will be shown after filtering and sorting? It would probably help a little with performance for long lists.

marekolszewski avatar Jul 30 '17 23:07 marekolszewski

@marekolszewski this probably won't happen, as the goal is to move away from doing manipulations to items inside Autocomplete. As I said above, it's fairly easy to simply limit the number of items before passing them to Autocomplete.

CMTegner avatar Aug 04 '17 10:08 CMTegner

FWIW, I landed on a different combination of libs because of this limitation and am switching off of react-autocomplete (20,000 items, so am imagining pretty rare :) if anyone else wanders across this issue, here's where I landed after a bit of searching:

https://codesandbox.io/s/LWMA9QZj

colinmegill avatar Jun 15 '18 01:06 colinmegill

@colinmegill Do you have an example with 20K items?

zachgibson avatar Jul 01 '18 16:07 zachgibson

@zachgibson this example also uses Downshift, and has about 1K entries :)

YoungElPaso avatar Dec 10 '18 20:12 YoungElPaso

@lilred work-arround/solution:

class LimitedAutocomplete extends Autocomplete {
  getFilteredItems(props) {
    let items = props.items

    if (props.shouldItemRender) {
      items = items.filter((item) => (
        props.shouldItemRender(item, props.value)
      ))
    }

    if (props.sortItems) {
      items.sort((a, b) => (
        props.sortItems(a, b, props.value)
      ))
    }

    return items.slice(0, props.limitItemsLength);
  }
}

Or

class LimitedAutocomplete extends Autocomplete {
  getFilteredItems(props) {
    const items = Autocomplete.prototype.getFilteredItems.call(this, props);
    return items.slice(0, props.limitItemsLength);
  }
}

FedeG avatar Jul 16 '19 17:07 FedeG