By Paul Scanlon

styled-components Responsive Array Syntax

In this post Iā€™m going to discuss a new approach Iā€™ve adopted when using [styled-components](https://Styled Components.com/), initially I wasnā€™t sure what this approach was called but I do really like this suggestion from Pedro Duarte at Modulz

From the outside when you use the Box component you can provide a width prop but instead of it simply being a string value itā€™s an array of values. Reading from left to right this will translate to the following:

  • 0: The Box will be width: 100%; on small screens
  • 1: The Box will be width: 50%; on medium screens
  • 2: The Box will be width: auto; on large screens

NB: This is assuming in your application you have a set of breakpoints defined, for example:

const breakpoints = ['576px', '768px', '992px'];

As my Tweet mentions both Chakra UI and Theme UI come with this kind of functionality built in, Styled Components does not. Thatā€™s not a bad thing, styled-components is different in many ways to Chakra UI and Theme UI, but rather than try to explain it myself hereā€™s the descriptions from each of the projects.

Chakra UI

ā€œChakra UI is a simple, modular and accessible component library that gives you all the building blocks you need to build your React applicationsā€

Theme UI

ā€œTheme UI is a library for creating themeable user interfaces based on constraint-based design principlesā€

styled-components

ā€œVisual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress šŸ’…šŸ¾ā€

The way I like to think about this is styled-components gives you the tools you need to build everything yourself where as Chakra UI and Theme UI give you the tools you need to build everything yourself + a load of stuff thatā€™s already built for you and can easily be re-themed.

If youā€™re using styled-components and like the look of the Responsive Array Syntax Iā€™m going to explain how to write a utility function that you can gradually adopt without breaking anything.

Before I go too much further when I use styled-components I donā€™t use template literals which is what youā€™ll see in the docs, instead I prefer to use the style object syntax.

Thereā€™s a PR for the docs site I created in Mar 2019 that attempts to explain the style object syntax which you can see here. I think I still need to do some work on that šŸ˜¬ ā€¦ so in the meantime Iā€™ve prepared a short post with the same usage examples: styled-components Style Objects

Ok, letā€™s start!

1.

Create a test component, Iā€™ve called mine <MrButton /> because Iā€™m a laugh.

// mr-button.js
import styled from 'styled-components';

const MrButton = styled.button({
  backgroundColor: 'hotpink',
  border: 'none',
  color: 'white',
  padding: 8,
  width: '100%',
});

export default MrButton;

2.

Make sure youā€™ve implemented <MrButton /> somewhere in your app and are passing in an array of values via the backgroundColor and width props.

// app.js
<MrButton backgroundColor={['hotpink', 'mouve', 'purple']} width={['100%', '50%', 'auto']}>
  Click Me
</MrButton>

3.

Create a util function somewhere and call it create-media-queries.js

// create-media-queries.js
export const createMediaQueries = (css) => {
  console.log(JSON.stringify(css, null, 2));
};

The first step is to make sure youā€™re able to pass the backgroundColor and width props through the styled component and on to the function. As mentioned above I prefer to use the style object syntax because I find it easier to destructure the props and to spread the return of the createMediaQueries function into the styles šŸ•ŗ

...
// mr-button.js
import styled from 'styled-components'
+ import { createMediaQueries } from '../utils'

const MrButton = styled.button(
  {
-   backgroundColor: "hotpink",
    color: "white",
    border: "none",
    padding: 8,
-    width: 100%
  },
+  ({ backgroundColor, width }) => ({
+    ...createMediaQueries([
+      {
+        property: "background-color",
+        values: backgroundColor
+      },
+      {
+        property: "width",
+        values: width
+      }
+    ])
+  })
);

export default MrButton;

If youā€™ve followed the steps above you should see a console.log like this: šŸ‘‡

[
  {
    property: 'background-color',
    values: ['hotpink', 'mouve', 'purple'],
  },
  {
    property: 'width',
    values: ['100%', '50%', 'auto'],
  },
];

Now youā€™ve confirmed the values passed in via the props get through the styled component and on to the createMediaQueries function you can do something with them, but before I move on letā€™s define an array of breakpoints. You could put this array anywhere in your project but just for demoā€™s sake Iā€™ll add it to the top of the create-media-queries.js file.

// create-media-queries.js
+ const breakpoints = [576, 768, 992]

export const createMediaQueries = (css) => {
  console.log(JSON.stringify(css, null, 2));
}

The aim now is to create key values pairs for each property and each of the values, youā€™ll also need to catch any values passed in that arenā€™t an array by using Array.isArray() and rather than .map them just return a key value pair.

// create-media-queries.js
const breakpoints = [576, 768, 992]

export const createMediaQueries = (css) => {
-  console.log(JSON.stringify(css, null, 2));

+  const cssKeyValuePairs = css.reduce((items, item) => {
+    const { property, values } = item;

+    items.push(
+      Array.isArray(item.values)
+        ? values.map((value) => ({
+            [property]: value
+          }))
+        : [{ [property]: values }]
+    );
+   return items;
+  }, []);

+  console.log(JSON.stringify(cssKeyValuePairs, null, 2));

}

Which should give you a console.log that looks like this: šŸ‘‡

[
  [
    {
      'background-color': 'hotpink',
    },
    {
      'background-color': 'mouve',
    },
    {
      'background-color': 'purple',
    },
  ],
  [
    {
      width: '100%',
    },
    {
      width: '50%',
    },
    {
      width: 'auto',
    },
  ],
];

The next part of the function will be responsible for assigning each of those values to an appropriate breakpoint value

// create-media-queries.js
const breakpoints = [576, 768, 992]

export const createMediaQueries = (css) => {

  const cssKeyValuePairs = css.reduce((items, item) => {
    const { property, values } = item;
    items.push(
      Array.isArray(item.values)
        ? values.map((value) => ({
            [property]: value
          }))
        : [{ [property]: values }]
    );
   return items;
  }, []);

-  console.log(JSON.stringify(cssKeyValuePairs, null, 2));

+  const cssToBreakpoints = [0, ...breakpoints]
+    .map((breakpoint, index) => ({
+      breakpoint: breakpoint,
+      css: cssKeyValuePairs
+        .map((array) => array[index])
+        .filter(Boolean)
+        .reduce((items, item) => {
+          items[`${Object.keys(item)}`] = `${Object.values(item)}`;
+          return items;
+        }, {})
+    }))
+    .slice(0, -1);

+  console.log(JSON.stringify(cssToBreakpoints, null, 2));

}

Which should give you a console.log that looks like this: šŸ‘‡

[
  {
    "breakpoint": 0,
    "css": {
      "background-color": "hotpink",
      "width": "100%"
    }
  },
  {
    "breakpoint": 576,
    "css": {
      "background-color": "mouve",
      "width": "50%"
    }
  },
  {
    "breakpoint": 768,
    "css": {
      "background-color": "purple",
      "width": "auto"
    }
  }
]
ā€‹

Thereā€™s a couple of things this part of the function does so Iā€™ll talk through them.

[0, ...breakpoints];

The above creates a new array and inserts a 0 at the beginning. Youā€™ll need the extra value at the start because youā€™re developing this function to be mobile first. The first value from the values array is the default style and isnā€™t part of a media query

breakpoint: breakpoint;

The above creates a key value pair for the breakpoint value, eg breakpoint: 0,

css: cssKeyValuePairs
  .map((array) => array[index])
  .filter(Boolean)
  .reduce((items, item) => {
    items[`${Object.keys(item)}`] = `${Object.values(item)}`;
    return items;
  }, {});

The above creates a new object called css and within it are the key values pairs from the first part of the function

.slice(0, -1);

Next youā€™ll need to remove the last item from the array since you shifted all the values up by one by inserting the 0 at the start of the arrayā€¦ I knowā€¦ mobile first is a bit confusing.

The final part of the function is where you can create formed media queries that will work when used with the style object syntax

// create-media-queries.js
const breakpoints = [576, 768, 992]

export const createMediaQueries = (css) => {

  const cssKeyValuePairs = css.reduce((items, item) => {
    const { property, values } = item;
    items.push(
      Array.isArray(item.values)
        ? values.map((value) => ({
            [property]: value
          }))
        : [{ [property]: values }]
    );
   return items;
  }, []);

  const cssToBreakpoints = [0, ...breakpoints]
    .map((breakpoint, index) => ({
      breakpoint: breakpoint,
      css: cssKeyValuePairs
        .map((array) => array[index])
        .filter(Boolean)
        .reduce((items, item) => {
          items[`${Object.keys(item)}`] = `${Object.values(item)}`;
          return items;
        }, {})
    }))
    .slice(0, -1);

-  console.log(JSON.stringify(cssToBreakpoints, null, 2));

+  const cssMediaQueries = cssToBreakpoints.reduce((items, item) => {
+    const { breakpoint, css } = item;
+
+    breakpoint
+      ? (items[`@media screen and (min-width: ${breakpoint}px)`] = {
+          ...css
+        })
+      : (items = { ...css });
+
+    return items;
+  }, {});

+  console.log(JSON.stringify(cssMediaQueries, null, 2));

+  return {
+    ...cssMediaQueries
+  };

}

Which should give you a console.log that looks like this: šŸ‘‡

{
  "background-color": "hotpink",
  "width": "100%",
  "@media screen and (min-width: 576px)": {
    "background-color": "mouve",
    "width": "50%"
  },
  "@media screen and (min-width: 768px)": {
    "background-color": "purple",
    "width": "auto"
  }
}

In this step youā€™ll use array reduce again to loop over the cssToBreakpoints array and ā€œmassageā€ it into a new shape.

The default css values are returned outside of a media query and the 2nd and 3rd set of values use the breakpoint number and return a formed media query with the correct css values injected

Iā€™m sure thereā€™s a million other ways to solve this problem but in my case it worked a treat. If you have any feedback or suggestions please feel free to find me on Twitter:

@pauliescanlon

Oh and if youā€™d like to dig a little deep hereā€™s a [CodeSandbox](https://codesandbox.io/s/Styled Components-responsive-array-syntax-3qxu3)

Hey!

Leave a reaction and let me know how I'm doing.

  • 0
  • 0
  • 0
  • 0
  • 0
Powered byNeon