Here is a simple grid system for react-native that I developed for my own needs
Usage
Example of two-column grid, each column is 50%:
<Grid size={2}> <Col> //some content </Col> <Col> //some content </Col> <Col size={2}> //This column fills the whole row </Col> <Col> //some content </Col> </Grid>
Three-column grid:
<Grid size={3}> <Col size={2}> //This column fills 2/3 of a row </Col> <Col> //This column fills 1/3 of a row </Col> <Col size={3}> //This column fills the whole row </Col> <Col> //This column fills 1/3 of a row </Col> <Col size={2}> //This column fills 2/3 of a row </Col> </Grid>
Available properties
For Grid component:
- size – amount of columns in one row, 1 by default
- colSpace – distance between columns, 15 by default
- rowSpace – distance between rows, 15 by default
- padded – if true, apply padding (horizontal is equal to colSpace and vertical is equal to rowSpace) to grid, false by default
- liquid – if true, columns will be distributed to fill width of the container (justifyContent: “space-between”), false by default
- allowEmpty – if false and grid does not have non-null child elements, render null (no empty View). False by default
For Col component:
- size – width of column, 1 by default. For example, if Grid’s size property is 2 and Col’s size property is 1, column will take half of the row (1/2). If Grid’s size property is 2 and Col’s size property is 2, column will fill the whole row
- expand – fill all available space in row, default is false
- allowEmpty – if false and column does not have non-null child elements, render null (no empty View). False by default
Code
import React, { useState, useCallback, useMemo } from "react"; import { View, StyleSheet } from "react-native"; import { styles } from "./Grid.styles"; import useIsMounted from "../hooks/useIsMounted"; import objectPath from "object-path"; import { number, bignumber, subtract, divide, multiply, floor } from "mathjs"; export const Grid = ({ size = 1, colSpace = 15, rowSpace = 15, padded = false, liquid = false, allowEmpty = false, children }) => { const isMounted = useIsMounted(); const [measuredWidth, setMeasuredWidth] = useState(null); const onLayout = useCallback(({ nativeEvent: { layout: { width } } }) => { if (isMounted.current) { setMeasuredWidth(width); } }, []); const dynamicStyles = StyleSheet.create({ grid: { marginRight: padded ? 0 : -colSpace, marginBottom: padded ? 0 : -rowSpace, paddingLeft: padded ? colSpace : 0, paddingTop: padded ? rowSpace : 0, justifyContent: liquid ? "space-between" : "flex-start" } }); return allowEmpty || React.Children.toArray(children).filter(el => React.isValidElement(el)) .length > 0 ? ( <> <View onLayout={onLayout} /> {typeof measuredWidth === "number" && ( <View style={[styles.grid, dynamicStyles.grid]}> {React.Children.map(children, el => { if (React.isValidElement(el)) { const newProps = { _gridProps: { size, colSpace, rowSpace, padded, liquid, measuredWidth: measuredWidth } }; if (el.type === Col && liquid) { newProps.size = objectPath.get(el, "props.size", null); } return React.cloneElement(el, newProps); } else { return el; } })} </View> )} </> ) : null; }; export const Col = ({ size = 1, expand = false, allowEmpty = false, children, _gridProps = {} }) => { const dynamicStyles = useMemo( () => StyleSheet.create({ col: { marginRight: _gridProps.colSpace, marginBottom: _gridProps.rowSpace, flexBasis: typeof size === "number" && typeof _gridProps.size === "number" && typeof _gridProps.colSpace === "number" && typeof _gridProps.measuredWidth === "number" && _gridProps.measuredWidth > 0 ? number( divide( floor( subtract( divide( multiply(bignumber(10000), bignumber(size)), bignumber(_gridProps.size) ), divide( multiply( bignumber(10000), bignumber(_gridProps.colSpace) ), bignumber(_gridProps.measuredWidth) ) ) ), bignumber(100) ) ) + "%" : null, flexGrow: expand ? 1 : null } }), [ _gridProps.colSpace, _gridProps.rowSpace, size, _gridProps.size, _gridProps.colSpace, _gridProps.measuredWidth ] ); return allowEmpty || React.Children.toArray(children).filter(el => React.isValidElement(el)) .length > 0 ? ( <View style={dynamicStyles.col}>{children}</View> ) : null; };
Grid.styles.js:
import { StyleSheet } from "react-native"; export const styles = StyleSheet.create({ grid: { flexDirection: "row", flexWrap: "wrap", alignItems: "stretch", justifyContent: "flex-start" } });
useIsMounted.js:
import { useEffect, useRef } from "react"; const useIsMounted = () => { const isMounted = useRef(true); useEffect(() => { isMounted.current = true; return () => { isMounted.current = false; }; }, []); return isMounted; }; export default useIsMounted;
Featured image source – pxhere.com