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.

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>
);
}
}
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>
)
}
}
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.
I would not use the upper div at all.
I would use full css to just float them next to eachother.
.col {
box-sizing: border-box;
width: 50%;
}
.col:nth-child(odd) {
margin-right: 0.5rem
}
.col:nth-child(even) {
margin-left: 0.5rem;
}
.col:not(:nth-child(0)),
.col:not(:nth-child(0)) {
margin-top: 1rem;
}
first .col needs a display: inline-block; can’t edit comments cry
I think this is ultimately the right answer. I was trying to imagine myself in a situation where I could not touch the CSS. If I had to do this render – for whatever reason – using logic instead of layout.
Also – the “OMG I can’t edit my comment” thing has gotten me too.
I know it may not optimal to load a whole package, but with React-Bootstrap you can easily use their column/row method without having to do all of this. A lot more modular too.
Why not use flexbox property of flex-flow: column wrap for layout instead? This would allow you to easily repeat your album component.
Yeah even better then my legacy CSS solution. At the office we only stopped support for IE9 and IE10 a month ago. Still need to get used to that you can then use flexbox.
Ooh – nice!
Like Samantha mentioned, I wonder how many people are still supporting IE. I did a session a few weeks ago at Nodevember, surveyed the room and there were a non-trivial amount of hands that went up saying they were still supporting old IE.
This is what I always default back to. As a functional programmer, it’s easy to default to using logic for almost everything. However, KISS dictates using what’s available rather than writing something new. CSS is incredibly powerful and, more often than not, most problems like these can be solved by simply using some HTML and CSS.
Additionally, if you’re using something novel like
styled-components
, you needn’t worry about access to the CSS files because they live within your styled components. :)This right here. I was going to ask if there was some strict requirement for the “row-twoColumns, row-twoColumns” structure. If there isn’t, I would think this is the best overall option, minus old browser support.
+1 for Flexbox. It was my initial thought as I was reading the opening paragraphs.
Unless you need to support the IE demons, there’s no need to bind yourself to a framework in this instance.
I had originally set this up as an instance where modifying the CSS is not an option. This was because I was using Bulma and out of the box, Bulma didn’t appear to support multi-line columns, even when specifying a width of 50% (
is-half
) and I didn’t want to monkey patch it.After Samantha kicked this off, I went back and looked at the docs and it turns out that Bulma totally CAN do it. All you have to do is add the
is-multiline
class.https://codepen.io/burkeholland/pen/XVrPON
RTFM, Burke!
Agree with Jennifer that it should be done with grid, flex or even float. Layout is not a JS concern. I don’t think that any framework would give you such limitations. You do not have to use all the classes they provide if it’s not suitable. I’m interested in why you think it’s not suitable for receiving streamed data. Can you explain that a bit?
I meant that my JSX rendering solution was not suitable. It operates on a fixed data set in order to determine when to open and close the “columns” tag. That means that if you had streaming data, as long as the sets coming in are all even, it’s all good. But the first time you have one with an odd number of items, the layout is jacked for the rest of the page.
Not sure what you mean by streaming data. The render function of a component will have access to a fix data set for each run. It will generate a shadow DOM and compare it to what’s in the actual DOM, then it replaces what’s need to be replaced.
For example:
– 1st render you have 2 items, so it renders two columns
– 2nd render you have 3 items, so it renders shadow DOM, realises it needs to render an extra row with only one column
– 3rd render you have 5 items, so it updates the second row and renders the third with a single column
You may have to port the logic from Angular to React see http://rintoj.github.io/angular2-virtual-scroll/
Neat plugin! Although React has a virtual scroll as well, I was more concerned with how I could conditionally wrap an unknown amount of elements in a tag.
There is also
react-virtualized
if you need virtualizationSince a JS solution was requested, but I’m on my phone, we’ll see if I can describe a solution.
Why not use a modulus operator to close the container div and open a new container for every list index that is even. Then appending new items to the list is trivial if their index stays consistent with the original list
I think that’s what I’m already doing, unless I’m reading this wrong. This works, but if the individual list changes (ala a stream of data instead of a fixed set), then the index goes out the window.
While I agree that CSS flexbox is probably the approach I would end up pursuing, I’m curious if it would work to use
map
to make the end of the list dynamic?P.S. forgive me for any syntax errors (I’m on a phone) and I really hope the come shows up because I can see that editing a comment is not available
Ahhh and of course there’s a typo. In the PS…
String.replace("m", "d", "come")
The code didn’t seem to fully render on my phone but it conveys the idea
Yes! This does work to eliminate the second
<Album>
tag in solution 2. However, it proposes a new problem – now the first iteration doesn’t have a way to define akey
because it’s the 2-dimensional array. Also, the rendering syntax almost looks harder to read now.What do you think?
We have a problem like this at my company that we published a react component to do, because I wasn’t happy with the alternatives. It supports multi-layout masonry-lite logic with infinite scrolling, in that it removes elements from the DOM as you scroll down to reduce pressure on the browser. You can see it in action here:
https://www.themaven.net/globallead
Just keep scrolling down :)
And github (the public code is actually a bit out of date, if anyone’s interested email and I’ll republish the latest from our private repo):
https://github.com/themaven-net/cascade
This is VERY interesting. I notice a little bit of a pop during the scroll. Is that caused by the masonry layout?
I would rather not reinvent the wheel and use the React-bootstrap library’s grid system to tackle this problem. It has options for you to set the number of columns shown in large, medium and small screens, making everything seamless.
I am not familiar with React, but just in general I believe it is better to use a list (ul,ol) to list items then use CSS to change the layout to whatever you want.
It makes sense semantically and is easier to manipulate.
That might be the case – I’m definitely not “Mr. Best Practice” by any stretch. Is there a specific reason why it’s bad to have a “repeater” that is not a list? I can’t think of any.
Disclaimer: I only started working as developer not long ago so I am by no mean an expert in the subject.
I don’t think there is any problem for repeating elements without using list per se, but in this example the items are wrapped based on the layout instead of their contexts.
Which is not that different from this
This groups every 2 items into one group, implying a relationship between the 2 items, which is not true in this case. A more accurate representation is:
Apart from more accurately representing the data, it is also more flexible.
Let’s say we want to implement a filter that will hide item 2.
With the
< ul>
you can simply remove the< li>
.With the columns
< div>
you will have to empty the list then repopulate it without item 2 to not leave an empty space on row 1It’s not bad but a list is better. With voice you can say “select 5th from list” for example. Also other sites can use your list, for example: google uses the index list of wikipedia in those blue links at the bottom of a search result.
I think I would use Python to solve this problem.
Python is always the answer. The question is irrelevant.
I forked your example and it seems like it works fine to just put multiple Albums in each column. https://codepen.io/dna113p/project/editor/ApjRav
OMG – this is really good. Odd numbers go in the first column and evens in the second.
What happens for the “unknown bottom” scenario though? If you end on an odd number, that will go in column one. When the next set of items comes in, you’ll start with one again and that one will also go in the first column.
I am not really sure about the unknown bottom thing and I don’t know much about RxJS, it seems to me that as long as you have a way to add the newly streamed items into your albums array using setState then react will properly render the new albums.
There are lots of good answers in here that approach this from the CSS side, which is probably the right way to do it, but in the interest of answering your question, even if it’s not the “right” question to ask…
There are two scenarios where dealing with arrays in react get weird…
The first is when you want to generate 0 or more entries from each element of the array. In this case, you should use:
The second is when you want to group array items, either by count, or by some other thing. In this case, you have a couple choices. You can still use the array reduce trick, and only “push” when you reach your threshold (good enough for simple cases) or you can use generators as converters (better for complex logic).
reduce technique: https://codepen.io/jamon/pen/VywvXL
generator technique: https://codepen.io/jamon/pen/QaWbPd
So this answer was super fascinating for me – particularly the
reduce
function because it’s not something that I have ever used.In the case of the
reduce
, is there a way to make the accumulator global so that if a new chunk of items came in on a stream, they could just be added to it?Also, what is the advantage of reduce over map? Is it performance since you only return the accumulator instead of each individual item?
to clarify around the “bottomless” part, you should let react handle this part for you–when you’re near the bottom, ask for more results, render more results. If you want to manage memory better, you can “free” results from the DOM that aren’t on-screen and offset the positions to compensate (this part is a hard problem too, but it’s a hard problem that’s widely solved with lots of examples to be found)
The “unknown bottom” is a red herring, you shouldn’t care about that; once you enter “infinite scroll” territory, you will need to keep additional state (i.e. fetched albums, scroll position) and render based on that.
To get back to your initial puzzle, the DRYest solution I found was to use lodash/chunk and two maps:
https://codepen.io/otsdr/project/editor/XLpxdO
“Now assume the CSS framework that you are using requires you to render out…”
Hahahhahahahaha…. no.
Those ridiculous requirements, tho!
But srsly – I used to work in healthcare and we had to jump through hoops that were beyond silly because of all the constraints that you couldn’t control. This example is contrived, but the struggle is real.
Shouldn’t it just work this way?
Column 1a
Column 2a
Column 1b
Column 2b
Column 1c
Column 2c
… ?
So many people seem to be missing the entire point of the puzzle. The point is to NOT change the CSS. That’s the “puzzle” part. The point is to stay within the confines of the layout described. You don’t go do the NY Times crossword puzzles and just rearrange the blocks to fit your answers, do you?
In any event, I’m not a fan of additional render functions inside components. I think it complicates the logic and it’s much clearer to just break those render functions into their own components.
I’m also not a fan of “precomputing” the state, as the second example does. It also complicates things. We can always use
componentShouldUpdate
to prevent unnecessary recalculations.I think a simplified version of the first approach with a single render function would be what I would go with. Something like:
I few issues I noticed:
Array indexes shouldn’t be used as keys.
It seems the suggested implementations assume an even number of items? If there are an odd number of albums, the last album wouldn’t be rendered.
I’m going to agree with you on the additional render functions. Render is an abstraction. Each time you add another render function, you create another abstraction and that makes it harder to reason about the original render.
Interesting thoughts on “pre-computed” state. I don’t like it either considering it’s yet another mutation that you have to be aware of in order to understand the entire flow.
I learned several things in this comment. Thank you!
I didn’t paste the important code from my example inline, which I guess would probably be helpful to people reading this on mobile or otherwise:
this allows
This has nothing to do with the coding itself, but I appreciate your music choice :)
#neverForget
React 16 allowes to set where in dom component should be rendered.
So each odd Column will normally render . .columns > .column.
Each even will render .column into the last .columns
Hmm.
Since the entire point is that the CSS can’t be changed:
Which also leaves me able to say at a later point, if it’s supported by the underlying CSS, to change it from 2-up to 3-up or other such things (change @columns on the Grid tag), or the CSS changes out in such a way that constructing the DOM in a certain way to get those columns isn’t necessary, for example:
Then:
One thing that I really like about this solution is that the Grid is now a separate component, which is going to simplify the render logic. You only need to reason that there is a grid. If you want to know how the grid works, you look at the grid component.
Can’t you just use flexbox, or rely on CSS for this in some other way? Trying to solve it with divs is mixing concerns and is what’s causing you problems in the first place.
You’re definitely right. The point I was trying to make here is that sometimes we don’t have control over the environment and we have to get creative. In my professional experience, I was more often given a giant mess and told to “make this work” than I was a blank slate.
I’m interested in the discussion, for sure, but mostly wanted to mention your good taste in music.
;)