Blog » Moving from Sass to Styled Components (with snapshot tests)

Moving from Sass to Styled Components (with snapshot tests)

2018-08 |

Ta treść jest dostępna tylko w języku angielskim

Moving from Sass to Styled Components (with snapshot tests)

Who doesn't like to constantly rework perfectly fine stuff into something new and fancy just because it's trendy now? Well, probably pretty much every single JS developer, most certainly everyone who works with this language long enough to experience at least a glimpse of famous "JS fatigue" feeling (so approximately a few weeks). However, I created this blog as a playground to try out new libraries and frameworks, and since I'm learning Styled Components for my professional work at the moment, even though Sass-based styling worked perfectly for my needs, I reimplemented all of it into this controversial CSS-in-JS. And I'm still sane!

Styled Components is one of many implementations of an idea of CSS-in-JS: concept, where you don't artificially map styling of components to components themselves, instead you treat both as one, and put it all in your JS/TS file. It's currently a hot topic in JavaScript world, at least for a year already. There are many advocates for this approach, many opponents, and probably twice as many libraries that bring it to life. Styled Components, with almost 20k stars on GitHub at the time of writing this post, must be one of the most widely used. It's popularity has been one of major reasons behind selecting it in our front-end development in StepStone.

After a few days of learning and coding, I must admit, I see some profits coming out of it (don't mistake it with strong recommendation or even opinion; I don't have one yet), outside of tight coupling components with their styles, which makes a lot of sense to me, especially in the context of React, and being up to date with fancy trends around the world. It forced me to rethink not only actual CSS code I have, but also separation of components inside my application: I created a few new components along the way, thus now I have smaller, more specialized, cleaner entities here and there, and I consider it a gain.

As usual, I implemented it within React and TypeScript project - this blog, the one you're reading. I'll display examples taken out of it's flesh, and you can always refer to more detailed implementation on my GitHub, where this application resides on average day.

Without further ado, let's dive straight into code samples.

Rewriting variables into theme

I started with reading the documentation. Once I got a grasp of it, I proceeded to installation of the package.

npm install --save-dev styled-components

Foundation of my Sass-based styling of this blog was file named variables.scss, where I stored standardized sizes of all elements, colors, spacings, fonts, etc. Naturally, I started with implementation of something analogical in Styled Components. In there, it's called theme, and it has it's own section in documentation.

Originally, my variables.scss file was:

$mobileBreakPointWidth: 968px;

$infoSectionWidth: 32%;
$contentSectionWidth: 100% - $infoSectionWidth;

$blue: rgb(0, 1, 49);
$darkBlue: darken($blue, 10%);
$darkerBlue: darken($blue, 20%);
$lightBlue: lighten($blue, 10%);
$lighterBlue: lighten($blue, 20%);

$white: rgb(246, 246, 246);
$darkWhite: darken($white, 10%);
$darkerWhite: darken($white, 20%);

$black: rgb(20, 20, 20);
$lightBlack: lighten($black, 10%);
$lighterBlack: lighten($black, 20%);

$gray: rgb(144, 144, 144);

$baseDistance: 8px;

$marginXXS: 0.25 * $baseDistance;
// ...

$paddingXXS: 0.25 * $baseDistance;
// ...

$fontSizeS: 1.5 * $baseDistance;
// ...

When I attempted to redo it as a theme, which is basically JavaScript object, I stumbled upon first issue: I couldn't perform basic mathematical operations on percents and pixels, as I could in Sass. Instead of having base distance defined as 8 pixels and multiplying it by any number without care, I had to define value of base distance as 8 outside of my theme, perform any calculation on pure number, and add px to the result. Template literals came to the rescue.

Another issue I discovered was using some of Sass native functions, such as lighten or darken. They're not supported in Styled Components by default. There are some additional packages, mainly Polished, prepared by authors of Styled Components themselves, which provide such functionality. However, at this point I had my first reflection on styling of my blog. Why do I even bother with this kind of operation for just a couple of variables? I decided that instead of that, I'd rather have all my colors defined explicitly.

Finally, my theme.ts ended up looking like this:

const infoSectionWidthPercentageValue = 32;
const baseDistanceValue = 8;
const baseFontSizeValue = 18;
const baseLineHeightValue = 24;

export const theme = {
  mobileBreakPointWidth: '968px',

  infoSectionWidth: `${infoSectionWidthPercentageValue}%`,
  contentSectionWidth: `${100 - infoSectionWidthPercentageValue}%`,

  blue: 'rgb(0,1,49)',
  darkBlue: 'rgb(0,0,24)',
  darkerBlue: 'rgb(0,0,0)',
  lightBlue: 'rgb(0,2,98)',
  lighterBlue: 'rgb(0,3,147)',

  white: 'rgb(246,246,246)',
  darkWhite: 'rgb(222,222,222)',
  darkerWhite: 'rgb(198,198,198)',

  black: 'rgb(20,20,20)',
  lightBlack: 'rgb(32,32,32)',
  lighterBlack: 'rgb(44,44,44)',

  gray: 'rgb(144,144,144)',

  baseDistance: `${baseDistanceValue}`,

  marginXXS: `${0.25 * baseDistanceValue}px`,
  // ...

  paddingXXS: `${0.25 * baseDistanceValue}px`,
  // ...

  baseFontSize: `${baseFontSizeValue}px`,
  baseLineHeight: `${baseLineHeightValue}px`,

  fontSizeS: '0.7em',
  // ...
};

You might have noticed another difference between the two: in Sass I used pixels as my font sizes, now I decided to go for "ems". That's another side effect of my reconsideration of my whole stylesheet. Since I couldn't just go for math operation on any unit like a mad man, I stopped for a second and thought again about this, read some articles and guidelines, and ended up deciding in favor of scalable unit.

Styling components

Having my variables redefined as Styled Components theme, I could proceed to getting rid of all SCSS files, replacing them with TS files. Hence, I asked myself a question: how do I provide my new variables to my new styles? Again documentation about theming came handy - theme is provided through component's props. I only had to wrap my root component in ThemeProvider, coming from Styled Components.

import theme from 'common/theme';

// ...

const Blog = () => {
  return (
    <ThemeProvider theme="{theme}">
      // ...
    </ThemeProvider>
  );
};

Thanks to this solution, I could now not only use variables, but also access all the other props in my styles. Not that I've used this feature, but I realize, that it opens another dimension of possibilities in their customization.

Before modifications, style for one of my components - Quote, piece of text visible on left hand side of the screen in desktop view - looked like this:

@import 'components/Blog/variables.scss';

.quote {
  margin: $marginM;
  padding: $paddingS;
  font-style: italic;
  text-align: center;
}

@media (max-width: $mobileBreakPointWidth) {
  .quote {
    display: none;
  }
}

After addition of Styled Components, analogical file's content was:

import styledComponents from 'styled-components';

export const StyledQuote = styledComponents.p`
  margin: ${props => props.theme.marginM};
  padding: ${props => props.theme.paddingS};
  font-style: italic;
  text-align: center;

  @media (max-width: ${props => props.theme.mobileBreakPointWidth}) {
    display: none;
  }
`;

As you can see, I could pretty much copy-paste my whole original style into template literal, which, decorated by function from Styled Components, became actual styled component. There's also type of component specified in there - it's a paragraph.

Now, instead of using traditional HTML notation in my rendering function:

import './styles.scss';

export const Quote = (): JSX.Element => {
  return (
    <p className="quote">
      <Label name="quote"/>
    </p>
  );
};

I used component generated by Styled Components:

import { StyledQuote } from './styled';

export const Quote = (): JSX.Element => {
  return (
    <StyledQuote>
      <Label name="quote"/>
    </StyledQuote>
  );
};

Voila!

However, not always it was such a piece of cake. In some cases, in my SCSS files I had nested selectors, with styling for specific child elements. At first I was fighting it, I tried to somehow preserve this configuration. I figured, that I still can define such class-based selectors within my Styled Components, and attempted to go with it. I quickly realized that I don't like it - I had components wrapped in nice Styled Component decorator, without any class definition, inside which there were divs with specific classes... This combination looked just wrong.

Then I understood, that my separation of components is straight up wrong in those cases. If there was a component that rendered specific divs with different, explicit styling... Why those divs are not independent components? So I made them independent components, I got rid of this styling issue, and I ended up with much cleaner code in some areas. That's, by the way, what I consider the greatest improvement to my blog's code base that came out of this whole Styled Components experiment.

Once I was done with it, I could see my blog in almost the same shape as it was originally, styled by Sass (only difference being sizes of fonts here and there, which was due to change from pixels to "ems", and was expected). Finally, I could remove all SCSS files, and all CSS and SCSS loaders both from my Webpack configuration and from my dependencies.

Code quality

Nevertheless of this change, I didn't want to make it at the cost of consistency of my code. Previously, I used StyleLint to ensure that all my SCSS files conform to the same standard. I was thinking if I can get the same cool feature for Styled Components, and I was happy to discover, that it comes almost out of the box. There is a package that integrates StyleLint with Styled Components, so that linting styles worked for me exactly the same as before: stylelint-processor-styled-components. It's not a part of main Styled Components package, but it's created by the same contributors, and hosted from the same account on GitHub.

At this point I was able to ensure quality of my styling code at the same level as before the change to Styled Components, but I was very happy to discover, that I can go even further than that. All thanks to jest-styled-components package, which is also created by the same group of people responsible for Styled Components.

I already had Jest snapshot tests written for all my components, in order to ensure consistency and control over tree of DOM elements rendered by them. Why wouldn't I have similar snapshot dedicated for styling generated for such component? I never thought about it before, but now that I unearthed it, I immediately wanted to have it. It's simple to generate, check, and modify during development, and also very beneficial. So I went for it.

Unfortunately, I didn't manage to successfully run setup presented in the documentation of this library, where they simply import it into their test files, and obtain styling of the component in their snapshot. I spent some time researching it and concluded that it might be caused by Enzyme or TypeScript, which I'm using. Not discouraged, I simply created separate tests to check my styles.

import * as React from 'react';
import { shallow } from 'enzyme';
import 'jest-styled-components';

import theme from 'common/theme';
import { StyledQuote } from 'components/Quote/styled';

describe('<StyledQuote/>', () => {

  it('is styled correctly', () => {
    expect(shallow(<StyledQuote theme="{theme}"/>)).toMatchSnapshot();
  });

});

Running test displayed above resulted in following snapshot:

exports[` is styled correctly 1`] = `
.c0 {
  margin: 16px;
  padding: 8px;
  font-style: italic;
  text-align: center;
}

@media (max-width:968px) {
  .c0 {
    display: none;
  }
}

<p
  className="c0"
/>
`;

This way, I not only completely reimplemented Sass-based styling with Styled Components for my whole project, but also improved it by adding some extra snapshot tests, covering styling of my components - area, which was not tested at all prior to this experiment. I learned something new, my blog got some bonus points for being trendy, and in the process, quite unexpectedly, I must admit, it also became tested more thoroughly.

That was a good exercise. I learned a lot, and I hope you can take something out of it too.

References

Inne wpisy

Webpack 4 config explained (with example)

2018-04 |

Ta treść jest dostępna tylko w języku angielskim

Webpack 4 config explained (with example)

Using a skeleton for your application prepared by someone else comes with great benefit of a lot of time saved, but also with huge cost of a lot of knowledge not obtained. Sometimes you'll manage to complete your assignment just fine with some predefined boilerplate, without too much need for deep investigation of it's nooks and crannies. Other times, you'll end up in a position, where you reverse engineer it in order to introduce some major change, or just give up and start from scratch with your own thing. I wouldn't like you to give up on my application skeleton. Thus, I'll describe some of it's shenanigans in it's documentation. Today, I'm explaining build process.

Czytaj dalej

Syntax highlighting in React with highlight.js and Web Worker

2018-09 |

Ta treść jest dostępna tylko w języku angielskim

Syntax highlighting in React with highlight.js and Web Worker

One of the most common issues I heard people have with my blog was lack of syntax highlighting in posts, especially those, which contain a lot of code. Okay, it's almost 2019, I'm a software engineer, working mostly with front-end these times - I finally agreed, that it should be added. So I added it. And in the meantime, I also learned a little bit about Web Workers. Hence, this post, in which I describe this little adventure.

Czytaj dalej

How to integrate Disqus comments in React app

2017-09 |

Ta treść jest dostępna tylko w języku angielskim

How to integrate Disqus comments in React app

One of the challenges that I faced while programming this, so far, very simple blog app (the one that you are using to read this post, most likely), was how to give my readers a possibility to comment on my posts. Obviously, it is one of the crucial features of this kind of app – I would like my readers to be able to tell me where am I wrong, and I would like me to be able to respond to such heretic claims. However, the whole feature seems quite a lot of work to implement from scratch. Luckily, there are plenty of ready out of the box solutions out there, one of them being Disqus.

Czytaj dalej

Hard to imagine, but it's over 1 year since I created this blog, and up until recently, it always had static head tags. Title always being the same, for example, wasn't that much of an issue to me, but social meta tags never related to the content of the post I'm sharing on Twitter, that was not cool (it's also not cool when it comes to SEO, but it's not that much of my concern right now). I finally had to tackle it. Here's a simple way to do it that I've found.

Czytaj dalej

How to setup routing for Not Found on both sides with React-Router and Express

2018-03 |

Ta treść jest dostępna tylko w języku angielskim

How to setup routing for Not Found on both sides with React-Router and Express

When building web applications in React, I usually choose Express to be my server, and more often than not I use React-Router to manage redirections and changes in history. Not without a reason - both are among the most popular choices in their respective fields nowadays; both are simple and elegant in every day work. However, I had some tough moments with both of them when it came up to setting all unrecognized routing to "Not Found" page, and this piece came as a result of them.

Czytaj dalej