Taylor Austin
Taylor Austin

Reputation: 6007

React Native 2x2, 3x3, 4x4 etc... (scalable grid) with gap and no spacing on the outside?

So I have gotten really close here: https://snack.expo.dev/@taustinligonier/f42d1f to get this working. I seem to be able to easily get the grid size I want (2x2, 3x3, etc...). However when it comes to adding a gap to only the middle part of the grid (no padding/margin on the outside) I have come to a complete stop.

I thought that doing margin on the children and -margin on the parent would work, but after some research negative margin on the bottom and right don't work the same as the top and left so I get some weird results (as in the wrapping no longer works for flex).

I could calculate the width of the item and use justify-content: space-between, but I think I can only do that horizontally or vertically not both with flex.

I can get this "working" with margin and padding but then there is spacing on the outside of the children which is what I don't want.

Maybe there is a way to add another container or something that would make it "look" like the children are touching the outer edge, but really they are not?

If you have any ideas I would be delighted to know! Thanks in advance.

Upvotes: 1

Views: 650

Answers (1)

PhantomSpooks
PhantomSpooks

Reputation: 3560

I think using flex on all components in a grid and using justifyContent:'space-between' are incompatible. Flex distribute the remaining width/height base upon the flex ratios and space-between shares the remaining width/height on the axis between all non first/last styled components. Because flex has already used the remaining width/height to increase the components size, then there will be no remaining width/height to use to add spacing between the components.

Here's where I realized this.

What I came up with was to apply margin to the opposite axis, e.g marginVertical is applied to the row, and marginHorizontal to columns. It works out ok-ish:

import * as React from 'react';
import { Text, View, StyleSheet, ScrollView } from 'react-native';
import Constants from 'expo-constants';
import { colorGenerator } from '@phantom-factotum/colorutils';
import Grid from './components/Grid';

const items = colorGenerator(11).map((color, i) => {
  return {
    title: 'Item' + (i + 1),
    color,
  };
});

export default function App() {
  return (
    <ScrollView style={styles.container}>
      <Grid
        rows={3}
        columns={4}
        items={items}
        containerStyle={{
          height: 400,
          width: '100%',
          backgroundColor: 'pink',
          marginVertical: 15,
        }}
        rowStyle={{ marginVertical: 10 }}
        columnStyle={{ marginHorizontal: 5 }}
      />
      <Grid
        rows={4}
        columns={3}
        items={items}
        containerStyle={{
          height: 400,
          width: '100%',
          backgroundColor: 'lightgreen',
          marginVertical: 10,
        }}
        rowStyle={{ marginVertical: 20 }}
        columnStyle={{ marginHorizontal: 5 }}
      />
      <Grid
        rows={2}
        columns={6}
        items={items}
        containerStyle={{
          height: 400,
          width: '100%',
          backgroundColor: 'lightblue',
          marginVertical: 10,
        }}
        rowStyle={{ marginVertical: 20 }}
        columnStyle={{ marginHorizontal: 5 }}
      />
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: Constants.statusBarHeight,
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
  paragraph: {
    margin: 24,
    fontSize: 18,
    fontWeight: 'bold',
    textAlign: 'center',
  },
});
import * as React from 'react';
import { Text, View, StyleSheet, Image } from 'react-native';

const isRowEdge = (i, totalRows) =>
  i % totalRows == 0 || i % totalRows == totalRows - 1;

export default function Grid({
  rows = 3,
  columns = 3,
  items,
  containerStyle,
  itemStyle,
  rowStyle,
  columnStyle,
}) {
  const initialValue = Array(rows)
    .fill([])
    .map(() => Array(columns).fill({}));
  let rowIx = 0;
  const renderRows = items.reduce((prev, curr, i) => {
    let index = i % columns;
    if (i % columns == 0 && i > 0) {
      rowIx++;
    }
    prev[rowIx][index] = curr;
    return prev;
  }, initialValue);
  console.log('rows:', renderRows.length, '\ncolumns:', renderRows[0].length);
  return (
    <>
      <Text>
        Rows:{rows} Columns:{columns}
      </Text>
      <View style={[styles.container, containerStyle]}>
        {renderRows.map((cols) => {
          return (
            <View style={[styles.row, rowStyle]}>
              {cols.map((item) => {
                return (
                  <View
                    style={[
                      styles.col,
                      styles.item,
                      columnStyle,
                      { backgroundColor: item.color },
                      !item.title && styles.emptySpace,
                    ]}>
                    <Text>{item.title || ''}</Text>
                  </View>
                );
              })}
            </View>
          );
        })}
      </View>
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  row: {
    flexDirection: 'row',
    // flex: 1,
    width: '100%',
    justifyContent: 'space-between',
    margin: 2,
  },
  col: {
    flexDirection: 'column',
    justifyContent: 'space-between',
    margin: 2,
    flex: 1,
    height: '100%',
  },
  item: {
    borderWidth: 1,
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  emptySpace: {
    borderWidth: 0,
  },
});

Upvotes: 1

Related Questions