4 Patterns for Responsive Props in React
Tags: react, responsive, css
Published: 2020-10-15 18:35:35

## Table of Contents

* [Responsive CSS](#repsonsive-css)
* [Responsive Props](#responsive-props)
* [Responsive Prop Patterns](#responsive-prop-patterns)
    * [Conditional Rendering](#conditional-rendering)
    * [Breakpoint Props](#breakpoint-props)
    * [Object of Props](#object-props)
    * [Array of Props](#array-props)
* [A Note About SSR](#ssr)
* [Summary](#summary)
* [Responsive Prop Libraries](#responsive-libraries)

## Responsive CSS
<a name="responsive-css"></a>

Many solutions exist for writing responsive CSS in React.

- CSS-in-JS ([Emotion](https://emotion.sh/) and [styled-components](https://styled-components.com/) are two popular options)
- [Tailwind CSS](https://tailwindcss.com/)
- [CSS Modules](https://css-tricks.com/css-modules-part-3-react/)

If you feel like you need to use one, I recommend choosing something which supports reusable media queries. You don't want to be hard-coding breakpoint values all throughout your code (it's tedious to write, error prone, and difficult to change). 

**But you might not need to write _any_ responsive CSS.**

## Responsive Props
<a name="responsive-props"></a>

Responsive CSS in React has a glaring weakness: it doesn't allow you to responsively change the values of props. Instead of writing CSS wrapped in media queries, responsive props is a method where you specify the value of the prop for predefined breakpoints and logic exists somewhere that chooses the value associated with the active breakpoint.

Why would you want to do that? Let's say you have different button sizes: large, medium, small. You probably don't want all buttons to change size responsively the same way. You might want one button to be small in mobile layouts while another is medium. So instead of responsive CSS, what you really want is responsive props.

We'll introduce some patterns below which primarily differ in two ways:

1. How the different prop values are specified 
2. Where the logic exists for choosing the correct value

The code samples shown in this blog post are copied from a [sample app](https://github.com/justincy/react-responsive-props) that has working examples of all the patterns.

## Responsive Prop Patterns
<a name="responsive-prop-patterns"></a>

The foundation of responsive props in react is knowing whether a given media query (aka breakpoint) is active. Let's write a custom hook for that called `useMediaQuery()`.

```js
import { useEffect, useState } from "react";

/**
 * Custom hook that tells you whether a given media query is active.
 *
 * Inspired by https://usehooks.com/useMedia/
 * https://gist.github.com/gragland/ed8cac563f5df71d78f4a1fefa8c5633
 */
export default function useMediaQuery(query) {
  const [matches, setMatches] = useState(false);
  useEffect(
    () => {
      const mediaQuery = window.matchMedia(query);
      setMatches(mediaQuery.matches);
      const handler = (event) => setMatches(event.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    },
    [] // Empty array ensures effect is only run on mount and unmount
  );
  return matches;
}
```

It can be used like this:

```js
const isActive = useMediaQuery('(max-width: 640px)');
```

But remember we don't want to be littering our code with breakpoint values so let's create another hook which returns booleans for all of our defined breakpoints. We'll call it `useBreakpoints()`.

```js
import useMediaQuery from "./useMediaQuery";

/**
 * Get a set of boolean representing which breakpoint is active
 * and which breakpoints are inactive.
 *
 * Inspired by: https://github.com/contra/react-responsive/issues/162#issuecomment-592082035
 */
export default function useBreakpoints() {
  const breakpoints = {
    isXs: useMediaQuery("(max-width: 640px)"),
    isSm: useMediaQuery("(min-width: 641px) and (max-width: 768px)"),
    isMd: useMediaQuery("(min-width: 769px) and (max-width: 1024px)"),
    isLg: useMediaQuery("(min-width: 1025px)"),
    active: "xs"
  };
  if (breakpoints.isXs) breakpoints.active = "xs";
  if (breakpoints.isSm) breakpoints.active = "sm";
  if (breakpoints.isMd) breakpoints.active = "md";
  if (breakpoints.isLg) breakpoints.active = "lg";
  return breakpoints;
}
```

It's used like this:

```js
const {isXs, isSm, isMd, isLg, active} = useBreakpoints();
```

Those hooks can power all of the responsive prop patterns discussed below.

### Conditional Rendering
<a name="conditional-rendering"></a>

Conditional rendering is the pattern of specifying content that gets rendered at the different breakpoints. We can accomplish that in two different ways.

#### Conditional Rendering with a Hook

We can use the `useBreakpoints()` hook from above to do conditional rendering like this:

```jsx
const {isXs, isSm} = useBreakpoints();
return isXs || isSm ? <Button size="small" /> : <Button />; 
```

#### Conditional Rendering with a Component

We could also write a component which will do something similar for us. Let's call it `<Breakpoint>`.

```jsx
import useBreakpoints from "./useBreakpoints";

export default function Breakpoint({ at, children }) {
  if (!at) {
    console.error("<Breakpoint>: must specify a breakpoint for the `at` prop.");
  }
  const { active } = useBreakpoints();
  return active === at ? children : null;
}
```

Then we can use it like this:

```jsx
return (<>
  <Breakpoint at="xs">
    <Button size="small">Button</Button>
  </Breakpoint>
  <Breakpoint at="sm">
    <Button size="small">Button</Button>
  </Breakpoint>
  <Breakpoint at="md">
    <Button>Button</Button>
  </Breakpoint>
  <Breakpoint at="lg">
    <Button size="large">Button</Button>
  </Breakpoint>
</>);
```

In its naive form, the component version of this pattern can be quite verbose. [fresnel](https://www.npmjs.com/package/@artsy/fresnel) is a library which uses this pattern and provides additional props such as `greaterThan` and `between` which can decrease the amount of code you need to write.

Notice that with conditional rendering, we're not changing the value of props so much as changing what gets rendered. There are situations where that's exactly what we need, such as choosing whether to render the mobile or desktop menu.

```jsx
return isXs || isXm ? <Mobile /> : <Desktop />;
```

As shown with the button example above, the conditional rendering pattern doesn't fit as well when we just want to make small tweaks such as changing the size or position of components. For those situations, we have other patterns that just modify props.

### Breakpoint Props
<a name="breakpoint-props"></a>

Maybe we could have one prop for each breakpoint. So instead of just `size` we have `sizeXs`, `sizeSm` and so on. It would be used like this:

```jsx
<Button sizeXs="small" sizeSm="small" sizeMd="medium" sizeLg="large">Button</Button>
```

In terms of usage, that's quite a bit less verbose than the example for conditional rendering. What about the implementation?

In the naive form, the implementation of this is very verbose.

```jsx
import styles from "../Button.module.css";
import useBreakpoints from "../useBreakpoints";

const defaultSize = "";
const defaultColor = "#eee";

export default function ButtonNaive({
  sizeXs,
  sizeSm,
  sizeMd,
  sizeLg,
  colorXs,
  colorSm,
  colorMd,
  colorLg,
  children
}) {
  const { isXs, isSm, isMd, isLg } = useBreakpoints();
  let activeSize = defaultSize;
  let activeColor = defaultColor;
  if (isXs) {
    activeSize = sizeXs;
    activeColor = colorXs;
  } else if (isSm) {
    activeSize = sizeSm;
    activeColor = colorSm;
  } else if (isMd) {
    activeSize = sizeMd;
    activeColor = colorMd;
  } else if (isLg) {
    activeSize = sizeLg;
    activeColor = colorLg;
  }
  const buttonClasses = [styles.base];
  if (styles[activeSize]) {
    buttonClasses.push(styles[activeSize]);
  }
  return (
    <button
      className={buttonClasses.join(" ")}
      style={{ backgroundColor: activeColor }}
    >
      {children}
    </button>
  );
}
```

We can do much better with a dynamic lookup for the props.

```jsx
import styles from "../Button.module.css";
import useBreakpoints from "../useBreakpoints";

const defaultSize = "";
const defaultColor = "#eee";

export default function DynamicButton({ children, ...props }) {
  const { active } = useBreakpoints();
  // The active breakpoint comes out lowercase but for the props
  // the first letter of the breakpoint needs to be capitalized.
  const activeCapitalized = active[0].toUpperCase() + active[1];
  // Now we dynamically lookup the value of each responsive prop
  // according to the active breakpoint.
  const activeSize = props[`size${activeCapitalized}`] || defaultSize;
  const activeColor = props[`color${activeCapitalized}`] || defaultColor;
  const buttonClasses = [styles.base];
  if (styles[activeSize]) {
    buttonClasses.push(styles[activeSize]);
  }
  return (
    <button
      className={buttonClasses.join(" ")}
      style={{ backgroundColor: activeColor }}
    >
      {children}
    </button>
  );
}
```

That's a little better but there's still more logic than we'd like to have in our components, so let's move some of it into a hook.

```jsx
const defaultSize = "";
const defaultColor = "#eee";

function useResponsiveProp(props, propName, defaultValue) {
  const { active } = useBreakpoints();
  const activeCapitalized = active[0].toUpperCase() + active[1];
  return props[`${propName}${activeCapitalized}`] || defaultValue;
}

export default function DynamicButton({ children, ...props }) {
  const activeSize = useResponsiveProp(props, 'size', defaultSize);
  const activeColor = useResponsiveProp(props, 'color', defaultColor);
  const buttonClasses = [styles.base];
  if (styles[activeSize]) {
    buttonClasses.push(styles[activeSize]);
  }
  return (
    <button
      className={buttonClasses.join(" ")}
      style={{ backgroundColor: activeColor }}
    >
      {children}
    </button>
  );
}
```

If you still think that's too much logic for dumb presentation components then you could also create an HOC.

```jsx
export default MakeResponsive(Button, ["size", "color"]);

function MakeResponsive(WrappedComponent, responsiveProps = []) {
  function MakeResponsiveWrapper(props) {
    const { active } = useBreakpoints();
    const activeCapitalized = active[0].toUpperCase() + active[1];
    const modifiedProps = { ...props };
    // Process the responsive props to extract responsive values
    for (const prop of responsiveProps) {
      const breakpointProp = `${prop}${activeCapitalized}`;
      if (props[breakpointProp]) {
        modifiedProps[prop] = props[breakpointProp];
      }
    }
    return <WrappedComponent {...modifiedProps} />;
  }
  MakeResponsiveWrapper.displayName = `MakeResponsive(${
    WrappedComponent.displayName || WrappedComponent.name
  })`;
  return MakeResponsiveWrapper;
}
```

That lets our components be dumb again, but now we have an [HOC](https://www.robinwieruch.de/react-hooks-higher-order-components) :grimacing:.

### Object of Props
<a name="object-props"></a>

The same code which inspired the `useMediaQuery()` hook also introduced me to a new pattern: specifying values for each breakpoint and letting a hook choose those values.

#### useBreakpointValues()

We can use the `useBreakpoints()` hook to create another hook called `useBreakpointValues()` which accepts a map of breakpoints and values and returns the value for the breakpoint that is currently active.

```js
function useBreakpointValues(breakpointValues) {
  const { active } = useBreakpoints();
  return breakpointValues[active];
}
```

We could use that inside our components to make any prop accept responsive values.

```jsx
const defaultColors = {
  xs: "#eee",
  sm: "#eee",
  md: "#eee",
  lg: "#eee"
};

export default function Button({ size, color = defaultColors, children }) {
  const appliedSize = useBreakpointValues(size);
  const appliedColor = useBreakpointValues(color);
  const buttonClasses = [styles.base];
  if (styles[appliedSize]) {
    buttonClasses.push(styles[appliedSize]);
  }
  return (
    <button
      className={buttonClasses.join(" ")}
      style={{ backgroundColor: appliedColor }}
    >
      {children}
    </button>
  );
}
```

But that makes our component more complicated. I recommend keeping the component simple and using the hook outside of the component.

```jsx
const currentSize = useBreakpointValues({
  xs: 'small',
  sm: 'small',
  md: 'medium',
  lg: 'large'
});
return <Button size={currentSize}>Button</Button>;
```

Using this pattern, our components can remain dumb presentation components by moving the breakpoint logic into a custom hook that we use outside of the components.

#### <Responsive> Component

We could also build a component version of `useBreakpointValues()` which uses [render props](https://reactjs.org/docs/render-props.html) to create a responsive prop pattern I learned from [instructure-ui](https://instructure.design/#Responsive).

```jsx
import useBreakpointValues from "./useBreakpointValues";

export default function Responsive({ props, children, render }) {
  const appliedProps = useBreakpointValues(props);
  if (!(children || render)) {
    console.error("<Responsive> must be given a render prop or children prop.");
  }
  const renderFn = children || render;
  return renderFn(appliedProps);
}
```

We would use it like this:

```jsx
return (
  <Responsive props={{
    xs: 'small',
    sm: 'small',
    md: 'medium',
    lg: 'large'
  }}>
    {(size) => <Button size={size}>Button</Button>}
  </Responsive>
);
```

Why would you want to do that instead of the hook? Perhaps just personal preference. And when you have responsive props for multiple components, it could help avoid the need for coming up with unique names. You can see this in the following contrived example using different button sizes for three buttons. Using the `<Responsive>` component is a little more verbose, and the render props pattern is pretty awkward to type, but maybe you just really dislike coming up with creative variable names.

```jsx
const size1 = useBreakpointValues({...});
const size2 = useBreakpointValues({...});
const size3 = useBreakpointValues({...});
return (
  <div>
    <Button size={size1}>Button 1</Button>
    <Button size={size2}>Button 2</Button>
    <Button size={size3}>Button 3</Button>
  </div>
);

// Here's the same setup using <Responsive>
return (
  <div>
    <Responsive props={{...}}>
      {(size) => <Button size={size}>Button 1</Button>}
    </Responsive>
    <Responsive props={{...}}>
      {(size) => <Button size={size}>Button 2</Button>}
    </Responsive>
    <Responsive props={{...}}>
      {(size) => <Button size={size}>Button 3</Button>}
    </Responsive>
  </div>
);
```

#### HOC for Responsive Props

Maybe neither of those are quite what you're looking for because you really like HOCs. We can do that too.

Usage:

```jsx
<ButtonHOC size={{ xs: "small", sm: "small", md: "medium", lg: "large" }}>
  Button
</ButtonHOC>
```

Implementation:

```jsx
function MakeResponsive(WrappedComponent, responsiveProps = []) {
  function MakeResponsiveWrapper(props) {
    const { active } = useBreakpoints();
    const modifiedProps = { ...props };
    // Process the responsive props to extract responsive values
    for (const prop of responsiveProps) {
      if (props[prop]) {
        modifiedProps[prop] = props[prop][active];
      }
    }
    return <WrappedComponent {...modifiedProps} />;
  }
  MakeResponsiveWrapper.displayName = `MakeResponsive(${
    WrappedComponent.displayName || WrappedComponent.name
  })`;
  return MakeResponsiveWrapper;
}

const ButtonHOC = MakeResponsive(Button, ['size','color']);
```

Again, our component stays dumb while the HOC makes it smarter.

> TIP: If you dislike having to specify a value for each breakpoint, even when each breakpoint doesn't have a distinct value, then you could add some logic to `useBreakpointValues()` so that if a breakpoint value isn't specified it just uses the value for the breakpoint below it. Then you would only have to specify when the value changes. Example: `{xs: 'small', md: 'large'}`.

### Array of Props
<a name="array-props"></a>

If you like how the object props pattern moves the logic outside of the component but dislike having to specify the breakpoints each time by name, then you may like this variation where props are specified via an array.

```jsx
const size = useBreakpointValues([ "small", "small", "medium", "large" ]);
return <Button size={size}>Button</Button>;

// Or...

<Button size={[ "small", "small", "medium", "large" ]}>
  Button
</Button>
```

The downside of this pattern is that it's not explicit; e.g. it's not immediately clear which value is associated with which breakpoint.

## A Note About SSR
<a name="ssr"></a>

There is no screen on the server so no breakpoints will be active. The best way to handle this situation is to choose a breakpoint that is active by default. Make this choice carefully because it could impact SEO (particularly for search engines that don't execute JavaScript).

## Summary
<a name="summary"></a>

A few patterns exist for responsive props in React. While choosing which patterns to use, consider these characteristics.

<table>
<thead>
  <tr>
    <th></th>
    <th></th>
    <th>How props are specified</th>
    <th>Where the logic exists for choosing the active value</th>
  </tr>
</thead>
<tbody>
  <tr>
    <td colspan="2">Conditional Rendering</td>
    <td>Separately in each instance</td>
    <td>Outside the component</td>
  </tr>
  <tr>
    <td rowspan="4">Breakpoint Props</td>
    <td>Naive</td>
    <td>One prop for each breakpoint</td>
    <td>Inside the component</td>
  </tr>
  <tr>
    <td>Dynamic</td>
    <td>One prop for each breakpoint</td>
    <td>Inside the component</td>
  </tr>
  <tr>
    <td>Dynamic Hook</td>
    <td>One prop for each breakpoint</td>
    <td>Inside a hook</td>
  </tr>
  <tr>
    <td>HOC</td>
    <td>One prop for each breakpoint</td>
    <td>Inside an HOC</td>
  </tr>
  <tr>
    <td rowspan="3">Object of Props</td>
    <td>Hook</td>
    <td>An object</td>
    <td>In the component or in a hook</td>
  </tr>
  <tr>
    <td>Render-prop Component</td>
    <td>An object</td>
    <td>In the render-prop component</td>
  </tr>
  <tr>
    <td>HOC</td>
    <td>An object</td>
    <td>In the HOC</td>
  </tr>
  <tr>
    <td rowspan="3">Array of Props</td>
    <td>Hook</td>
    <td>An array</td>
    <td>In the component or in a hook</td>
  </tr>
  <tr>
    <td>Render-prop Component</td>
    <td>An array</td>
    <td>In the render-prop component</td>
  </tr>
  <tr>
    <td>HOC</td>
    <td>An array</td>
    <td>In the HOC</td>
  </tr>
</tbody>
</table>

My preference is to use the Object Props pattern with the `useBreakpointValue()` hook and `<Responsive>` component because I like the explicit nature of the props object and I like having the logic for choosing the active breakpoint value outside of my components.

What do you think? Which pattern do you like? Are there responsive prop patterns I didn't include? If you're not using responsive props, do you feel like you should? Let me know in the comments. Thanks for reading!

--------

## Responsive Prop Libraries
<a name="responsive-libraries"></a>

### Conditional Rendering

* [fresnel](https://www.npmjs.com/package/@artsy/fresnel)
* [react-responsive](https://www.npmjs.com/package/react-responsive)
* [react-media](https://github.com/ReactTraining/react-media) was an early player in this game. Unfortunately the library hasn't been updated for hooks.

### Breakpoint Props

* [Atomic Layout](https://redd.gitbook.io/atomic-layout/fundamentals/responsive-props)

### Object of Props

* [Responsive instructure-ui component](https://instructure.design/#Responsive)
* [responsive-props](https://www.npmjs.com/package/responsive-props) - an HOC that adds responsive props to [styled components](https://www.styled-components.com/).

### Array of props

* [Braid Design System](https://seek-oss.github.io/braid-design-system/foundations/layout)