# React Native - Dynamic Flatlist

### Introduction

Hi, I'm Fabian and this is my first blog post. I recently started learning about [React Native](https://reactnative.dev/). The other day, I was looking for a way to dynamically adjust the columns of a [React Native Flatlist](https://reactnative.dev/docs/flatlist) based on the screen size and orientation (horizontal or vertical) of the device. By the end of this article, you'll be able to create such a ```Flatlist```: 

![output-onlinegiftools (3).gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1664909975114/5BINw7fbr.gif align="left")

### Basics

First, let's start with a simple dataset for the ```data``` property of our ```Flatlist```. For this example, let's take a ```data``` array that contains objects that each have just a ```key``` with an increasing number.

Now, we can already represent this data with a few lines of code. In addition to the ```data``` property of the ```Flatlist```, we also need the ```renderItem``` prop, which in this case simply makes use of the keys of our array that we just created.

```
import { FlatList } from 'react-native';

const data = [
  { key: '1' }, { key: '2' }, { key: '3' }, { key: '4' }, { key: '5' }, 
  { key: '6' }, { key: '7' }, { key: '8' }, { key: '9' }
];

const App = () => {
    return (
      <FlatList
        data={data}
        renderItem={({item}) => { return item.key}}
      />
    );
}

export default App;
```

This list is still a long way from our desired result, but it already represents our desired data. If we now want to display this list in columns, we can use the ```numColumns``` property. For this, we define a constant with a value of ```3``` (we will change that in a moment).

At the same time, let's define a ```style``` that will center the objects, give them a background, and set them to a fixed height and width to form rectangles. In order for us to be able to see these, we also set a ```margin``` to 1. Furthermore, let's define a ```flex``` of 1 on a ```view``` enclosing the text to ensure automatic adjustment of the width of the rectangles.


```
const numColumns = 3;

const App = () => {
    return (
      <FlatList
        data={data}
        numColumns={numColumns}
        renderItem={({item}) => {
          return (
          <View style={styles.item}>
            <Text style={styles.itemText}>{item.key}</Text>
          </View>)
        }}
      />
    );
}


const styles = StyleSheet.create({
  item: {
    backgroundColor: '#A1A1A1',
    alignItems: 'center',
    justifyContent: 'center',
    flex: 1,
    margin: 1,
    height: 120,
    width: 90
  },
  itemText: {
    color: '#fff',
  },
});
``` 

### Empty items

Since we currently have nine elements, which we represent in three columns, we have three rows with three elements each. This makes everything look evenly distributed. However, if we now remove an element, we see that this is no longer the case.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1664913785367/Ubruoer7u.png align="center")

Due to the added ```flex``` of 1, ```React Native``` adjusts the ```width``` of each object of each row to fill the entire width of the screen (except the ```margin```). If this is your desired result, you can skip to the next header. If not, let's define a new function called ```formatData```. 

This function takes two parameters, ```data``` and ```numColumns```. Given these parameters, we can calculate how many empty objects we need to add to our existing ```data``` array to fill the last row. If, as in this case, we have two items in the last row, we want to add an empty object to that row to reflect the column count of ```3```.

```
const formatData = (data, numColumns) => {
  const amountFullRows = Math.floor(data.length / numColumns);
  let amountItemsLastRow = data.length - amountFullRows * numColumns;

  while (amountItemsLastRow !== numColumns && amountItemsLastRow !== 0) {
    data.push({key: `empty-${amountItemsLastRow}`, empty: true});
    amountItemsLastRow++;
  }
  return data;
};
```

This method first calculates the number of full rows and subtracts that multiplied by the number of columns from our total number of objects in the ```data``` array to calculate the number of objects left over.

Practically speaking, it looks at how many elements have already been *served *and still have to be *served*. This number can be either ```0``` if all have already been resolved, or less than the number of columns. Therefore, in the last step, new objects are added to the ```data``` array until the number of elements in the last row corresponds to the required number of columns (```amountItemsLastRow === numColumns```).

It is important to note that the added objects must also receive a unique ```key```. The boolean ```empty``` is used in the ```renderItem``` prop to display an empty object as such. To keep things simple, we're also relocating the ```renderItem``` as a standalone method.


```
const renderItem = ({ item, index }) => {
    if (item.empty) {
      return <View style={[styles.item, styles.itemTransparent]} />;
    }
    return (
      <View style={styles.item}>
        <Text style={styles.itemText}>{item.key}</Text>
      </View>
    );
  };

const App = () => {
    return (
      <FlatList
        ...
        renderItem={renderItem}
      />
    );
}
``` 

As you may have noticed, there is now an additional ```style``` that is applied in the ```renderItem``` function if the object has the boolean empty, and it is ```true``` to make the background transparent.

### Dynamic numColumns
Now let's get to the part we've been waiting for: dynamically adjusting the number of columns. In order to achieve this, we need to keep in mind what can happen and how we want our app to react. The user can rotate their phone so that the view can switch from vertical to horizontal mode or vice versa. Another example is Samsung's foldables, which can change their view by opening or closing the device.

Since this also changes the ```width``` and ```height``` of the ```window``` each time, we track the ```width``` using the [useWindowsDimesions](https://reactnative.dev/docs/usewindowdimensions) hook for this application. This allows the ```width``` to be destructured at the beginning of our component.

```
const {width} = useWindowDimensions();
``` 

By using the [React](https://reactjs.org/) [useState](https://reactjs.org/docs/hooks-reference.html#usestate) we can now replace the ```numColumns``` constant accordingly and set the initial state to a function that we will implement later in order to be able to calculate the number of columns depending on the ```width``` that has just been destructured.

``` 
const [numColumns, setNumColumns] = useState(calcNumColumns(width));
``` 

The [useEffect](https://reactjs.org/docs/hooks-reference.html#useeffect) hook helps us detect a change in the ```width``` variable through ```useWindowDimensions```, and then replace the ```numColumns``` with a recalculation using the updated width.

``` 
useEffect(() => {
    setNumColumns(calcNumColumns(width));
  }, [width]);
``` 

The only thing left now is to write our function for calculating the columns. First, we have to think about which calculation we want to use to determine the number. We calculate the not rounded count ```cols``` by dividing the ```width``` passed in by the ```width``` of one of our objects .

```
const calcNumColumns = (width) => {
  const cols = width / styles.item.width;
};
``` 

We also specify a constant ```colsFloor```, which either takes on the value of a fixed minimum number of ```numColumns```, or, if our just calculated ```cols``` is larger when rounded down, should equal this value.

```
const minCols = 2;

const calcNumColumns = (width) => {
  const cols = width / styles.item.width;
  const colsFloor = Math.floor(cols) > minCols ? Math.floor(cols) : minCols;
};
``` 

From this, we now have to subtract the total ```margin``` to calculate how many columns fit in one row. If we temporarily increase the ```margin``` to 15, for example, we see that the spacing at the edges of the screen is half the spacing between objects. 

![Untitled.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1664919665093/VYntkQQlA.png align="left")

This is because the ```margin``` applies to each object and is therefore duplicated between the objects themselves. We take this into account in our calculation by multiplying the rounded number of columns by the generated distance twice. This allows us to derive the total ```margin``` and subtract that from ```cols``` to know if our ```colsFloor``` fit into ```cols``` along with that margin.

```
const minCols = 2;

const calcNumColumns = (width) => {
  const cols = width / styles.item.width;
  const colsFloor = Math.floor(cols) > minCols ? Math.floor(cols) : minCols;
  const colsMinusMargin = cols - 2 * colsFloor * styles.item.margin;
  if (colsMinusMargin < colsFloor && colsFloor > minCols) return colsFloor - 1; 
  else return colsFloor;
};
``` 
If the resulting difference (```colsMinusMargin```) is smaller than the rounded one (```colsFloor```), this means that including our defined distance between the objects a maximum of ```colsFloor - 1``` elements will fit in one row, otherwise ```colsFloor```. The rest of the if ensures that we don't display fewer than our defined ```minCols``` columns. 

### Final fix
Now if we run this code like this and rotate our screen, we get an error message saying:

> Changing numColumns on the fly is not supported. Change the key prop on FlatList when changing the number of columns to force a fresh render of the component.

This can be fixed by setting the Flatlist's ```key``` prop to ```numColumns```.

### Everything put together

%[https://snack.expo.io/@blackcore/flalist-dynamic-numcolumns-depending-on-screensize-and-orientation]

Here you can finally try out for yourself how the flat list changes as a result of your own adjustments. If you want to rotate the screen or change the screen size, just open the app on your own mobile device or the [Snack webplayer](https://snack.expo.dev/@blackcore/flalist-dynamic-numcolumns-depending-on-screensize-and-orientation). After opening the link you can use the web player by clicking on the small icon to the right of the blue web button.

### Conclusion

With that we have come to the end. Thanks for reading my first blog article. If you have any suggestions for improvement or questions, please leave a comment. Have fun with the Flatlist and see you soon! 


