DEV Community

Cover image for React Native: Best Practices When Using FlatList or SectionList
Marco Ledesma
Marco Ledesma

Posted on

React Native: Best Practices When Using FlatList or SectionList

Have you had any performance issues when using React Native SectionList or FlatList? I know I did. It took me many hours and one time almost an entire week to figure out why performance was so poor in my list views (seriously, I thought I was going to lose it and never use React Native again). So let me save you some headaches (or maybe help you resolve existing headaches 😊) by providing you with a couple of tips on how to use SectionLists and FlatLists in a performant way!

(This article assumes you have some experience with React Native already).

Section List Example

Alt Text

Above is a simple app example where users manage their tasks. The headers represent “categories” for each task, the rows represent a “task” that the user has to do by what date, and the Check is a button that marks tasks as “done” – simple!

From a frontend perspective, these would be the components I would design:

  • CategoryHeader

    • Contains the Title and an arrow icon on the left of it.
  • TaskRow

    • Contains the task’s Title, details, and the Check button that the user can interact with.
  • TaskWidget

    • Contains the logic that formats my task data.

This also uses React Native’s SectionList component to render those tasks.
And here’s how my SectionList would be written in my TaskWidget:

    renderSectionHeader={( event ) => {
        return this.renderHeader( event ); //This function returns my `CategoryHeader` component
       {title: 'General Project Management', data: [ {...taskObject}, ...etc ]},
       ...additional items omitted for simplicity
    keyExtractor={( item ) => item.key}

Pretty straight forward right? The next thing to focus on is what each component is responsible for (and this is what caused my headaches).

Performance Issues

If we look at TaskRow, we see that we have several pieces of information that we have to display and calculate:

Alt Text

  1. Title
  2. Description
  3. Due date formatted
  4. Due date from now calculated
  5. “Check” button action

Previously, I would’ve passed a javascript object as a “prop” to my TaskRow component. Maybe an object that looks like this:

   "title": "Contact Joe Bob",
   "description:": "Need to talk about project assesment",
   "due_date": "2019-07-20"

I then would have my TaskRow display the first two properties without any modification and calculate the due dates on the fly (all this would happen during the component’s “render” function). In a simple task list like above, that would probably be okay. But when your component starts doing more than just displaying data, following this pattern can significantly impact your list’s performance and lead to antipatterns. I would love to spend time describing how SectionLists and FlatLists work, but for the sake of brevity, let me just tell you the better way of doing this.

Performance Improvements

Here are some rules to follow that will help you avoid performance issues in your lists:

I. Stop doing calculations in your SectionList/FlatList header or row components.

Section List Items will render whenever the user scrolls up or down in your list. As the list recycles your rows, new ones that come into view will execute their render function. With this in mind, you probably don’t want any expensive calculations during your Section List Item's render function.

Quick Story

I made the mistake on instantiating moment() during my task component's render function (moment is a date utility library for javascript). I used this library so I could calculate how many days from "now" my task was due. In another project, I was doing money calculations and date formatting in each of my SectionList row components (also using moment for date formatting). During these two instances, I saw performance drop significantly on Android devices. Older iPhone models were also affected. I was literally pulling my hair out trying to find out why. I even implemented Pure Components, but (like I’ll describe later) I wasn’t doing this right.

So when should you do these expensive calculations? Do it before you render any rows, like in your parent component’s componentDidMount() method (do it asynchronously). Create a function that “prepares” your data for your section list components. Rather than “preparing” your data inside that component.

II. Make your SectionList’s header and row components REALLY simple.

Now that you removed the computational work from the components, what should the components have as props? Well, these components should just display text on the screen and do very little computational work. Any actions (like API calls or internal state changes that affect your stored data) that happen inside the component should be pushed “up” to the parent component. So, instead of building a component like this (that accepts a javascript object):

<TaskRow task={taskObject} />

Write a component that takes in all the values it needs to display:

   onCheckButtonPress={ () => this.markTaskAsDone(taskObject) }

Notice how the onCheckButtonPress is just a callback function. This allows the component that is using TaskRow to handle any of the TaskRow functions. Making your SectionList components simpler like this will increase your Section List’s performance, as well as making your component’s functionality easy to understand.

III. Make use of Pure Components

This took a while to understand. Most of our React components extend from React.Component. But using lists, I kept seeing articles about using React.PureComponent, and they all said the same thing:

When props or state changes, PureComponent will do a shallow comparison on both props and state and many other React Native Posts

I honestly couldn’t follow what this meant for the longest time. But now that I do understand it, I’d like to explain what this means in my own words.

Let’s first take a look at our TaskRow component:

class TaskRow extends React.PureComponent {
   ...prop definitions...

   onCheckButtonPress={ () => this.markTaskAsDone(taskObject) }

TaskRow has been given props that are all primitives (with the exception of onCheckButtonPress). What PureComponent does is that it’s going to look at all the props it’s been given, it’s then going to figure out if any of those props have changed (in the above example: has the description changed from the previous description it had? Has the title changed?). If so, it will re-render that row. If not, it won’t! And it won’t care about the onCheckButtonPress function. It only cares about comparing primitives (strings, numbers, etc.).

My mistake was not understanding what they meant by "shallow comparisons". So even after I extended PureComponent, I still sent my TaskRow an object as a prop, and since an object is not a primitive, it didn’t re-render like I was expecting. At times, it caused my other list row components to rerender even though nothing changed! So don’t make my mistake. Use Pure Components, and make sure you use primitives for your props so that it can re-render efficiently.

Summary, TLDR

Removing expensive computations from your list components, simplifying your list components, and using Pure Components went a long way on improving performance in my React Native apps. It seriously felt like night and day differences in terms of performance and renewed my love for React Native.

I’ve always been a native-first type of mobile dev (coding in Objective C, Swift, or Java). I love creating fluid experiences with cool animations, and because of this, I’ve always been extra critical/cautious of cross-platform mobile solutions. But React Native has been the only one that has been able to change my mind and has me questioning why I would ever want to code in Swift or Java again.

Discussion (2)

gedalyakrycer profile image
Gedalya Krycer

Thank you for creating this post! It's 2021 and this taught me so much!

m4rcoperuano profile image
Marco Ledesma Author

Aw yay! I’m glad ^_^ @gedalya!