How Would You Solve This Rendering Puzzle In React?

Avatar of Burke Holland
Burke Holland on

Welcome, React aficionados and amateurs like myself! I have a puzzle for you today.

Let’s say that you wanted to render out a list of items in a 2 column structure. Each of these items is a separate component. For example, say we had a list of albums and we wanted to render them a full page 2 column list. Each “Album” is a React component.

Scroll rendering problem

Now assume the CSS framework that you are using requires you to render out a two column layout like this…

<div class="columns">
  <div class="column"> Column 1 </div>
  <div class="column"> Column 2 </div>
<div class="columns">

This means that in order to render out the albums correctly, you have to open a columns div tag, render two albums, then close the tag. You do this over and over until all the albums have been rendered out.

I solved it by breaking the set into chunks and rendering on every other album conditionally in a separate render function. That render function is only called for every other item.

class App extends Component { 
  state = {albums: [] }

  async componentDidMount() { 
    let data = Array.from(await GetAlbums());
    this.setState({ albums: data } ); 
  } 

  render() { 
    return (
      <section className="section">
        {this.state.albums.map((album, index) => {
          // use the modulus operator to determine even items return index % 2 ?
          this.renderAlbums(index) : '';
        })}
      </section> 
    )
  }

  renderAlbums(index) { 
    // two albums at a time - the current and previous item
    let albums = [this.state.albums[index - 1], this.state.albums[index]]; 
    
    return (
      <div className="columns" key={index}>
        {albums.map(album => {
          return (
            <Album album={album} />
          );
        })}
      </div>
    );
  }
}

View Full Project

Another way to do this would be to break the albums array up into a two-dimensional array and iterate over that. The first highlighted block below splits up the array. The second is the vastly simplified rendering logic.

class App extends Component { 
  state = {albums: []}
  
  async componentDidMount() { 
    let data = Array.from(await GetAlbums());
    
    // split the original array into a collection of two item sets
    data.forEach((item, index) => {
      if (index % 2) {
        albums.push([data[index - 1], data[index]]);
      }
    });

    this.setState({
      albums: albums
    });
  } 

  render() {
    return ( 
      <section className="section">
        {this.state.albums.map((album, index) => { 
          return ( 
            <div className="columns">
              <Album album={album[0]}></Album> 
              <Album album={album[1]}></Album> 
            </div> 
          ) 
        })}
      </section> 
    ) 
  } 
}

View Full Project

This cleans up the JSX quite a bit, but now I’m redundantly entering the Album component, which just feels wrong.

Sarah Drasner pointed out to me that I hadn’t even considered one of the more important scenarios here, and that is the unknown bottom scenario.

Unknown Bottom

Both of my solutions above assume that the results set received from the fetch is final. But what if it isn’t?

What if we are streaming data from a server (ala RxJs style) and we don’t know how many times we will receive a results set, and we don’t know how many items will be in a given set. That seriously complicates things and utterly destroys the proposed solutions. In fact, we could go ahead and say that neither of these solutions are ideal because they don’t scale to this use case.

I feel like the absolute simplest solution here would be to fix this in the CSS. Let the CSS worry about the layout the way God intended. I still think it’s important to look at how to do this with JSX because there are people building apps in the real world who have to deal with shenanigans like this every day. The requirements are not always what we want them to be.

How Would You Do It?

My question is just that — how would you do this? Is there a cleaner more efficient way? How can this be done so that it scales with an unknown bottom? Inquiring minds (mine specifically) would love to know.