Performant Lists in React with react-window

Alex Taylor

When building a web application with React, you sometimes have the need to display a large amount of data. Simply rendering it all in a list can become very slow, which can affect the rest of the application.

react-window seeks to solve this problem by rendering only the items in the list that are currently visible. This allows for efficiently rendering lists of any size.

You may be aware of a similar library, react-virtualized. The two libraries are actually made by the same person; react-window is a complete rewrite with the goal of being smaller, faster, and easier to learn. Because of these goals, however, react-window does not have every feature implemented in react-virtualized. Be sure to read the documentation and decide which library works best for you.

🐊 Alligator.io recommends

Recommended React and GraphQL course

Getting Started

To get us started, I’ve set up a small application using create-react-app and Ant Design. To see a more in-depth introduction to Ant Design, check out the blog post: Crafting Beautiful UIs in React Using Ant Design.

Here is what our root component looks like. All I’ve done so far is add a reference to the list component we’re about to create.

index.js

import React from "react";
import ReactDOM from "react-dom";
import List from "./list";

import "./styles.css";

function App() {
  return (
    <div className="App">
      <List numItems={100} />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Now we need to create our list.

Building the List Component

For this demo, I’ll be rendering a list of quotes with a small image next to them. The quotes will be provided by the random-quotes package, so install that first:

$ yarn add random-quotes

# or, using npm:
$ npm install random-quotes --save

Create a new file named list.js. All we need to do is pass our array of quotes into Ant Design’s <List /> component. For sake of clarity, I’ve extracted this into its own component called <RegularList />.

list.js

import React from "react";
import randomQuotes from "random-quotes";
import { List, Avatar } from "antd";

import "antd/dist/antd.css";

function RegularList(props) {
  return (
    <List
      dataSource={props.quotes}
      renderItem={item => (
        <List.Item>
          <List.Item.Meta
            avatar={
              <Avatar
                src="https://source.unsplash.com/random/300x300"
              />
            }
            title={item.body}
            description={item.author}
          />
        </List.Item>
      )}
    />
  );
}

export default class extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      quotes: []
    };
  }

  componentDidMount() {
    this.setState({
      quotes: randomQuotes(this.props.numQuotes)
    });
  }

  render() {
    return <RegularList quotes={this.state.quotes} />;
  }
}

Now our list is perfect! Or is it? Try bumping up the value for the numItems prop. You’ll see that large values (> 1000) can begin to lag quite terribly, depending on your computer.

Virtualizing our List with react-window

As you can see, I’ve increased numItems to 10,000:

index.js

// --- snip ---

function App() {
  return (
    <div className="App">
      <List numItems={10000} />
    </div>
  );
}

// --- snip ---

This is very, very slow when used with our <RegularList /> component. This is where react-window comes in! Of course, we first need to install it:

$ yarn add react-window

# or, with npm:
$ npm install react-window --save

react-window provides several different components, but we only need the simplest one, <FixedSizeList />. This needs the height, width, itemCount, and itemSize passed as props, and a function which we’ll call renderRow() passed as a child.

In the list.js file, create a new component called <VirtualList />:

list.js

import React from "react";
import randomQuotes from "random-quotes";
import { List, Avatar } from "antd";
import { FixedSizeList as VList } from "react-window";

// -- snip --

class VirtualList extends React.Component {
  renderRow = ({ index, key, style }) => {
    const quote = this.props.quotes[index];

    return (
      <List.Item key={key} style={style}>
        <List.Item.Meta
          avatar={
            <Avatar
              src="https://source.unsplash.com/random/300x300"
            />
          }
          title={quote.body}
          description={quote.author}
        />
      </List.Item>
    );
  };

  render() {
    return (
      <List dataSource={this.quotes}>
        <VList
          height={window.innerHeight}
          width={window.innerWidth}
          itemCount={this.props.quotes.length}
          itemSize={75}
        >
          {this.renderRow}
        </VList>
      </List>
    );
  }
}

export default class extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      quotes: []
    };
  }

  componentDidMount() {
    this.setState({
      quotes: randomQuotes(this.props.numQuotes)
    });
  }

  render() {
    return <VirtualList quotes={this.state.quotes} />;
  }
}

And we’re done! Try scrolling around, the slowdowns are completely gone!

However, you might notice list elements “popping in” if you scroll quickly enough. This is because the elements are being rendered a fraction of a second after they enter the screen. To prevent this from happening, you can simply add the prop overscanCount to the <VList />:

list.js

// --- snip ---

render() {
  return (
    <List dataSource={this.quotes}>
      <VList
        height={window.innerHeight}
        width={window.innerWidth}
        itemCount={this.props.quotes.length}
        itemSize={75}
        overscanCount={3}
      >
        {this.renderRow}
      </VList>
    </List>
  );
}

// --- snip ---

That’s it! This tells react-window to render an extra three items off-screen, so the pop-in isn’t so apparent.

To learn more about react-window see its website and its Github repository. The full code for this post is available on CodeSandbox.

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#