Creating a Reusable Grid System in React
Tags: react, tutorial, javascript
Published: 2020-07-16 18:55:36

The grid system is arguably the most valuable layout tool for building websites. Without it, responsive layouts would be, well, NOT responsive.

I use React a lot, so I decided to create a grid system that I could reuse in my React apps. It started as a personal tool, but as I got more use out of it, I decided to release it for other devs to use.

So I did. It's called React Tiny Grid, and it's a 12-column grid system that's pretty handy. You can find it [here](https://react-tiny-grid.netlify.app).

But today, we're going to rebuild it step-by-step, so you can follow along and see how it's built.

## Setup

We'll be using styled-components to style our grid system. Let's install that.

```
$ npm install --save styled-components
```

Now that we've got our dependencies installed, we'll create our two files: one for the Row component, and one for the Column component.

```
$ touch Row.js Column.js
```

## Basic Grid Functionality
To start, we'll create a basic flex wrapper that makes all the column items the same width, and wraps them.

### Creating the Row Component
Inside of our Row.js file, we'll outline the basic row component.

```
import  React  from  'react';
import  styled, { css } from  'styled-components';
import { Column } from  './Column';

export const Row = ({children}) => {
  return (
    <Wrapper>
      {React.Children.toArray(children).map((item) => {
        return (
          item && (
            <Column>
              {item.props.children}
            </Column>
          )
        );
      })}
    </Wrapper>
  );
};

const  Wrapper = styled.div`
  @media (min-width: 769px) {
    display: flex;
    justify-content: flex-start;
    flex-wrap: wrap;
    margin: 0 -8px 0 -8px
  }
`;
```

Let's break this down.

For the basic functionality, we're mapping through the `children` of this component, and making those each a Column (we'll style those later).

```
{React.Children.toArray(children).map((item) => {
  return (
    item && (
      <Column>
        {item.props.children}
      </Column>
    )
  );
})}
```

To add the grid functionality, we simply make the `<Wrapper>` a flex element.

```
const  Wrapper = styled.div`
  @media (min-width: 769px) {
    display: flex;
    justify-content: flex-start;
    flex-wrap: wrap;
    margin: 0 -8px 0 -8px;
  }
`;
```

We 'activate' the grid system once the screen size is wider than 769px. Then, we set the display to flex.

We also add the negative margin to account for the spacing of the columns (styled later).

```
margin: 0 -8px 0 -8px;
```

### Creating the Column Component

Now that we have our Row component, we need to style the Column component.

Inside of our Column.js file, we'll create the basic column markup and styles.

```
import  React  from  'react';
import  styled, { css } from  'styled-components';

export const Column = ({children}) => {
  return (
    <Wrapper>{children}</Wrapper>
  );
};

const Wrapper = styled.div`
  flex: 1 1 0;
  width: 100%;
  padding: 8px;
`;
```

All we have to do for now is give the Column the ability to resize equally with it's siblings. This is accomplished using the `flex` property.

```
flex: 1 1 0;
```
We also added 8px of padding to each column. If you recall, that's the amount of negative margin we added to the `Row` component. This is to make sure the edges of the columns meet the edges of their parent container.

## Supporting Custom Breakpoints

So far, we've got an automatic grid system! The columns are all resized, and are full-width on mobile.


But a REAL grid system supports custom breakpoints. So let's do that now.

Inside of our `Row.js` file, we'll accept a `breakpoints` prop, with a default value of 769.

```
export const Row = ({children, breakpoints = [769]}) => {
  ...
};
```
Now, we can use this breakpoints array to decide when to active the grid. To do this, we pass the first item in the `breakpoints` array to the `<Wrapper>` component.

```
export const Row = ({children}) => {
  return (
    <Wrapper breakpoint={breakpoints[0]}>
      ...
    </Wrapper>
  );
};
```

Then, we replace the 769px media query with a template literal, which are supported by styled-components. This allows us to use our breakpoint value.

```
const  Wrapper = styled.div`
  @media (min-width: ${props => props.breakpoint}px) {
    ...
  }
`;
```

Now, we can pass in a custom breakpoint to our `Row` component.

```
<Row breakpoints={[960]} />
```

But you know what would be cool?

Custom column widths. For each breakpoint 🤯

Let's do that now!

### Custom Widths
Back inside of our `Column.js` file, we need to accept two new props: first, a `breakpoints` array, which will be passed down from the parent `Row` component. Second, a `widths` array, which will contain an array of numbers defining how many columns to take up.

```
export const Column = ({children, breapoints, widths = ['auto']}) => {
  ...
};
```

> Note: we used a default value of auto for the widths: this will allow the column to take up whatever space is available, in case we forget to pass in a widths prop.

Now, we're setting up the grid system to support up to three custom breakpoints and widths. However, we need to make sure that we have a default value for each of these three, in case we forget to pass in a value.

At the top of our `Column` component, we'll add these variables.

```
const  breakpointOne = breakpoints[0];
const  breakpointTwo = breakpoints.length >= 1 ? breakpoints[1] : null;
const  breakpointThree = breakpoints.length >= 2 ? breakpoints[2] : null;
  
const  widthOne = widths[0];
const  widthTwo = widths.length >= 1 ? widths[1] : null;
const  widthThree = widths.length >= 2 ? widths[2] : null;
```

Basically, what we're doing is checking if there are 3 width values. If not, we set the third value to the previous width item. That way, our grid won't break!

Now, we need to pass in these values as props to the column `<Wrapper>` component.

```
export const Column = ({children, breakpoints, widths = ['auto']}) => {
  return (
    <Wrapper
      breakpointOne={breakpointOne}
      breakpointTwo={breakpointTwo}
      breakpointThree={breakpointThree}
      widthOne={widthOne}
      widthTwo={widthTwo}
      widthThree={widthThree}
    >
      {children}
    </Wrapper>
  );
};
```

This will allow us to change the width of the column based on specific breakpoints.

Inside our `Wrapper` styled-component, let's add media queries.

```
const Wrapper = styled.div`
  flex: 1 1 0;
  width: 100%;
  padding: 8px;

  // ACTIVE BETWEEN BREAKPOINT ONE AND TWO (OR 9999PX)
  @media(min-width: ${props => props.breakpointOne}px) and
  (max-width: ${props => props.breakpointTwo | 9999}px) {
    width: ${props => props.widthOne !== 'auto'
      ? `${(props.widthOne / 12) * 100}%`
      : null};
    flex: ${(props) => (props.widthOne !== 'auto' ? 'none !important' : null)};
  }

  // ACTIVE BETWEEN BREAKPOINT TWO AND THREE (OR 9999PX)
  @media(min-width: ${props => props.breakpointTwo}px) and
  (max-width: ${props => props.breakpointThree | 9999}px) {
    width: ${props => props.widthTwo !== 'auto'
      ? `${(props.widthTwo / 12) * 100}%`
      : null};
    flex: ${(props) => (props.widthTwo !== 'auto' ? 'none !important' : null)};
  }

  // ACTIVE BETWEEN BREAKPOINT THREE AND UP
  @media(min-width: ${props => props.breakpointThree}px) {
    width: ${props => props.widthThree !== 'auto'
      ? `${(props.widthThree / 12) * 100}%`
      : null};
    flex: ${(props) => (props.widthThree !== 'auto' ? 'none !important' : null)};
  }
`;
```

Ok. That's a lot to look at.

The first thing we make sure to do is add a `max-width` to the media query. This is to make sure the `flex` property does NOT get reset if the width value is 'auto'.

The main thing we have to take note of is the function used to calculate the width of the column. Since we use a 12-column grid, we get this value by taking the width (a value from 1-12) and dividing it by 12. We multiply THAT number by 100 to get the percentage.

```
width: ${props => props.widthThree !== 'auto' ? `${(props.widthThree / 12) * 100}%` : null};
```

We also add a ternary operator to make sure the width is still 100% if the column width is auto by setting the width value to null.

Now, the final thing we need to do is pass the breakpoints from the `Row` component to the `Column` component.

Inside of our `Row.js` file, we'll update the return statement.

```
return (
  {React.Children.toArray(children).map((item) => {
    return (
      item && (
        <Column
          breakpoints={breakpoints}
          {...item.props}
        >
          {item.props.children}
        </Column>
      )
    );
  })}
)
```

And viola! Now, we're able to use custom breakpoints and widths for our grid system.

```
<Row breakpoints={[576]}>   
 <Column widths={[4]} />  
 <Column widths={[8]} />  
 <Column widths={[3]} />  
 <Column widths={[9]} />  
 <Column widths={[7]} />  
 <Column widths={[5]} />  
</Row>
```

## Conclusion

So now, we have a fully-functioning React grid system. If you want even more functionality, like custom spacing, offsets, and more, check out [React Tiny Grid](https://react-tiny-grid.netlify.app).

You can find the full code for this grid system on [Github](https://github.com/jarodpeachey/react-tiny-grid/tree/master/packages/core/src).

If you liked this tutorial and found React Tiny Grid useful, I'd appreciate it if you could [buy me a coffee](https://www.buymeacoffee.com/jarodpeachey)!

If you've got any questions or improvements for the grid system, you can comment that down below.