By Paul Scanlon

Styled Components Responsive Array Syntax

  • React
  • JavaScript
  • Styled Components

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](https://Styled Components.com/docs/basics#getting-started), 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](https://github.com/Styled Components/Styled Components-website/pull/444). 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