React Render Props with Ts & styled-components | Part 1
Tags: react, typescript, layout, styledcomponents
Published: 2021-01-14 22:23:52

## What are we building
A React component that allows the *setting up of a grid*. For more flexibility, you will have two different methods for rendering the content:
1. render props
2. builder method (part 2)

## Quick setting up
* `npx create-react-app [name] --template typescript`
* Cleaning the boilerplate
* Install `styled-components` & `@types/styled-components`

> or clone from [repo/boilerplate](https://github.com/didof/react-render-props-grid-layout/tree/boilerplate)

## What we need
We need a component that can be instructed on the **number of columns**. It will be able to **derive the width** it occupies **inside the DOM** and with a simple division it will draw the width (*pixel*) assigned to each child. This last information will be displayed through the **render props** and can be used immediately with great flexibility.

## Method
Create in `src/components/GridLayout`
* `GridLayout.types.ts`
* `GridLayout.styles.ts`
* `GridLayout.tsx`

The component categorically needs the number of columns. If necessary, it also allows you to set the minimum height of each grid tile. Above all, it not only wraps the children but **injects** an intermediate step in the form of a **function that exposes the width** of each tile.

######`GridLayout.types.ts`
```ts
export interface GridLayoutProps {
    columnsAmount: number;
    rowHeight?: number;
    children: (width: number) => JSX.Element | JSX.Element[]
}
```

***

Now we define the *styled component* that will be used in the component. For simplicity here the styling is done all in one go - however it is easy to imagine a streamlining thanks to [advanced features](https://styled-components.com/docs/advanced).

######`GridLayout.styles.ts`
```ts
import styled from 'styled-components'

type GridProps = {
    columnsAmount: number;
    rowHeight?: number;
}

export const Grid = styled.div <GridProps>`
    width: 100%;
    height: 100%;

    display: grid;
    
    justify-content: center;
    align-items: center;
    
    grid-template-columns: ${({ columnsAmount }) => `repeat(${columnsAmount}, 1fr)`};
    grid-auto-rows: ${({ rowHeight }) => rowHeight ? `${rowHeight}px` : 'auto'};

`
```

Basically, it is a grid, which places the various children each in the center of their own piece. The number of columns is decided from the outside. Same thing happens for the size of each row, with the difference of a fallback on auto in case of no definition.

***

It's time to put the pieces together and build the magic component. It may seem overwhelming at first glance but let's break it down like this:
1. Props provided and where they are used
2. Get the width of the component
3. Calculate the size of each grid tile
4. Render Props

*See you after the code block*

#####`GridLayout.tsx`
```ts
import { useState, useEffect, createRef } from 'react'
import { GridLayoutProps } from './GridLayout.types'

import { Grid } from './GridLayout.styles'

const GridLayout = ({ children, columnsAmount, rowHeight }: GridLayoutProps): JSX.Element => {

    const gridRef = createRef<HTMLDivElement>()
    const [elementWidth, setElementWidth] = useState<number>(0);

    useEffect(() => {
        const { current } = gridRef
        let gridWidth = current!.getBoundingClientRect().width
        setElementWidth(Math.round(gridWidth / columnsAmount));
    }, [columnsAmount, rowHeight, gridRef])

    return (
        <Grid
            columnsAmount={columnsAmount}
            rowHeight={rowHeight}
            ref={gridRef}
        >
            {children(elementWidth)}
        </Grid>
    )
}

export default GridLayout

```

#### 1. Props provided and where they are used
It receives the props defined in `GridLayout.types.ts`.
Putting children aside for a moment, `columnsAmount` and `rowHeight` are both passed to `<Grid>` (I mean the styled component). And here the grid is done. Now let's fill it.

#### 2. Get the width of the component
This point is slightly more tricky. We need to somehow access the width of the component. So we associate a ref to the outermost component (<Grid>).

> Why the signature on `createRef()`? Explained [here](https://medium.com/@martin_hotell/react-refs-with-typescript-a32d56c4d315)

However, the `current` property of the `ref`, i.e. the **DOM node** itself, is *undefined* until the component has been mounted by React.
For this we move to `useEffect(..., [...dependencies])`. It runs after the component has been mounted, so we can access the current. We don't stop there, but we use the `getBoundingClientRect()` method and extract the width of the component from the result. Let's not forget to save it in a variable.

> For the sake of simplicity, in case of window resizing, the value is not updated and the result (as you will be able to experience shortly) will be mined. I leave you the pleasure of solving it and this nice [hook](https://usehooks.com/useWindowSize/). Be careful not to prick yourself.

#### 3. Calculate the size of each grid tile
<center>
We have the width of the container<br />+<br />we know how many equal pieces to split it<br />=<br />how wide each piece is.
</center>
Let's save the result of this division (via `useState`, I recommend) somewhere.

#### 4. Render Props
Here we are again at the prop not yet used, `children`. Remember it's a function. So where we would have simply done `{children}`, this time do `{children ()}`. And *typescript* gives us error -  it remember that we have instructed it that it is a function that requires a parameter of type number. And here's where we put the last piece of the puzzle `{children (elementWidth)}`.

# Epilogue

We import `GridLayout` in `App.tsx`. Let's build a list of colors (in this case only 2, alternating). Finally, we use `GridLayout`, telling it we want 8 columns. Inside we invoke the function that gives us the width of each element that we can use at will.

```ts
import React from 'react'

import GridLayout from './components/GridLayout/GridLayout'

const colors = ['black', 'brown']

const itemList = (amount: number, shift: number): string[] => {
  let i = amount;
  let output = [];
  let toggle = 0;
  while (i > 0) {
    if(i % shift !== 0) toggle = Boolean(toggle) ? 0 : 1
    output.push(colors[toggle])
    i--;
  }
  return output;
}

const App = () => (
  <div>
    <h1>React Render Props Grid Layout</h1>
    <GridLayout
      columnsAmount={8}
    >
      {(itemWidth) => React.Children.map(itemList(64, 8), color => (
        <div
          style={{
            width: itemWidth,
            height: itemWidth,
            backgroundColor: color
          }} />
      ))}
    </GridLayout>
  </div>
)

export default App;
```

> All the code in the [repo/render-props-1](https://github.com/didof/react-render-props-grid-layout/tree/render-props-1)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/68thwwwqe1sryk864z2w.png)

***

Rather than returning a bland `div`, go wild. Also, try to change the window size and refresh the page.

> Why **children**? Because it is *special* and allows you to do this thing to open braces and start writing inside a component.
If you had chosen another name - for example builder(?) - you would have had to invoke the function as the value of a prop:
```ts
// example
<GridLayout
   builder={(foo) => <div>{foo}</div>}
/>
```

> This topic is covered in [part 2](https://dev.to/didof/react-render-props-with-ts-styled-components-part-2-4n84)

***

### Useful links
* [how to make a good grid](https://css-tricks.com/snippets/css/complete-guide-grid/)
* [Making peace between ref and typescript](https://www.pluralsight.com/tech-blog/getting-size-and-position-of-an-element-in-react/#:~:text=position%20in%20React.-,Getting%20the%20Size%20and%20Position,a%20reference%20to%20that%20element.)
* [Styled-components with typescript](https://blog.agney.dev/styled-components-&-typescript/)
* [useState and typescript](https://www.carlrippon.com/typed-usestate-with-typescript/)
* [Render Props](https://medium.com/@jrwebdev/react-render-props-in-typescript-b561b00bc67c)
* [Render Props - the Return](https://blog.logrocket.com/react-reference-guide-render-props/)
* [repo](https://github.com/didof/react-render-props-grid-layout/tree/render-props-1)

***

### Connect with Me:
GitHub - [didof](https://github.com/didof)
LinkedIn - [Francesco Di Donato](https://www.linkedin.com/in/francesco-di-donato-2a9836183/)
Twitter - [@did0f](https://twitter.com/did0f)