Chris Kohler

Navigate back to the homepage

Lessons learned from building a Grid List in React Native

Chris Kohler
June 5th, 2020 · 2 min read

tl;dr

Use the power of flexbox and aspectRatio to build a dynamic grid which works for all screens and orientations.

Goal

goal

We want to build a photo grid which is easy to use, supports both portrait and landscape orientation and has configurable columns.

Flatlist

https://reactnative.dev/docs/flatlist makes it really easy to implement a photo grid. It handles all the hard work of big lists and comes with build in support for columns. We use Flatlist to build our grid.

Simple Flatlist Example

This is a full app example how to use a Flatlist. Try it out on expo.io

1import * as React from "react";
2import { Image, FlatList } from "react-native";
3
4const picsumImages = new Array(11).fill("http://placeimg.com/640/360/any");
5
6function renderItem({ item }) {
7 return <Image source={{ uri: item }} style={{ height: 100 }} />;
8}
9
10export default function App() {
11 const [images, setImages] = React.useState(picsumImages);
12 return <FlatList data={images} renderItem={renderItem} />;
13}

We only need to provide those attributes to make it work:

  • data -> array of items we want to iterate over
  • renderItem -> the component we want to render per item

The result is a list of images with a height of 100 and stretched to full width.

simple example

Flatlist with columns

To create a grid from the previous example is straight forward. We only have to define the number of columns:

1<FlatList data={images} renderItem={renderItem} numColumns={4} />

👉 If you coded along you might have noticed that the screen is now white and no images are displayed. The is because we didn’t define a width for the individual items.

Let’s just add a fixed width for now:

1<Image source={{ uri: item }} style={{ height: 100, width: 100 }} />

The result is a grid. But since the width is fixed to 100 the last image is clipped. Open in snack.expo.io

clipped

Set tile size with dimension api

One approach to fix the clipping problem is to read the width of the screen and then calculate the the tile width:

1import { Image, Dimensions } from "react-native";
2
3const screenWidth = Dimensions.get("window").width;
4const numColumns = 4;
5const tileSize = screenWidth / numColumns;
6
7<Image source={{ uri: item }} style={{ height: tileSize, width: tileSize }} />;

The result is a nice photo grid that works. Here is a working example

dimension api

What I don’t like about the solution is that I have to calculate the tile size manually. With my web background I always prefer a fluid solution.

Set tile size with flexbox

React Native comes with a great support for flexbox. So let’s get rid of the dimension api and replace it with flexbox.

1<Image source={{ uri: item }} style={{ height: 100, flex: 1 }} />

So my first approach gives me this result. Live example

flex1

There are two problems here:

  • The height is fixed which breaks the aspect ratio of 1
  • If the number of items can not be divided by the number of columns the bottom items are stretched

Introducing the aspect ratio

The aspect ratio problem is easy to fix. Just remove the height property and define the aspectRatio:

1<Image source={{ uri: item }} style={{ aspectRatio: 1, flex: 1 }} />

aspect ratio

Live example (Make sure you run it in the simulator since the web view doesn’t support the aspectRatio property)

Using flex with 1/numColumns

There are at least two ways how to fix the stretched bottom items issue:

  • Add fake empty items to fill it up
  • Use flex 1/numColumns

I want to focus on the flex/numColumns solution.

It’s actually pretty simple. Just set the flex to 1/numColumns

1const numColumns = 4;
2
3<Image
4 source={{ uri: item }}
5 style={{ aspectRatio: 1, flex: 1 / numColumns }}
6/>;

num columns

Here is a live example (Make sure you run it in the simulator since the web view doesn’t support the aspectRatio property)

Summary

Flatlist makes it very easy to build a photo grid with React Native. flexbox helps to create fluid layouts without the need to know the exact dimensions of the screen.

Creating a grid is a very common problem and I hope I could show you an easy and robust way how to do that.

If you liked the article 🙌, spread the word and follow me on Twitter for more posts on React Native, Angular and web technologies.

Did you find typos 🤓? Please help improve the blogpost and open an issue here

Final code

1import * as React from "react";
2import { Image, FlatList, Dimensions } from "react-native";
3
4const picsumImages = new Array(11).fill("http://placeimg.com/640/360/any");
5
6const numColumns = 4;
7
8function renderItem({ item }) {
9 return (
10 <Image
11 source={{ uri: item }}
12 style={{ aspectRatio: 1, flex: 1 / numColumns }}
13 />
14 );
15}
16
17export default function App() {
18 const [images, setImages] = React.useState(picsumImages);
19 return (
20 <FlatList data={images} renderItem={renderItem} numColumns={numColumns} />
21 );
22}

More articles from Chris Kohler

Reactive Angular with ngrx/component

One step closer to a zone-less Angular

March 22nd, 2020 · 6 min read

How to use ResizeObserver with Angular

Observe resize events on elements with Angular

February 24th, 2020 · 3 min read
© 2020 Chris Kohler
Link to $https://twitter.com/kohlerchristianLink to $https://github.com/christiankohlerLink to $https://instagram.com/mrchriskohlerLink to $https://www.linkedin.com/in/mr-christian-kohler/