Why JSS?

The Problem

At Ginkgo I work on a front-end repo called Portal that is shared by a few teams. We have a microservice architecture, where each application is served by a separate back-end. Portal is a single-page application that presents a unified front-end for our users – biologists working to engineer organisms and organize lab operations. As many of you probably know, when a number of teams work on a single repo, it is hard to communicate all of the changes that might need to occur. This includes styling.

Styling for Portal has evolved over the years since I have been working on it. We started with vanilla CSS in index.css files. That was initially a problem for us as the CSS files were becoming too big and we also wanted to be able to nest styles. We then moved to SCSS so that we could have nesting and styling variables. We also moved more towards component specific SCSS files, rather than an index file for the whole directory.

Here is an example of a small SCSS file for a component:

.header-table-wrapper {
  .select-info-text {
    color: $mid-gray;
    font-size: 0.9rem;
  }
  .header-table-actions {
    display: inline-block;
    width: 100%;
    padding: 10px 0;
    position: relative;
    min-height: 40px;

    button {
      margin-bottom: 0 !important;
      vertical-align: bottom;
      margin-right: 5px;
    }

    p {
      display: inline-block;
      font-size: 0.9rem;
      color: $dark-gray;
      margin-bottom: 0;
    }
  }
}

As you can see we have the entire SCSS file wrapped by the .header-table-wrapper selector. By adding this class to the component itself, it ensures that the button and p styling we have enclosed in it will only be applied to that component.

But what happens if we forgot to enclose that button styling in the wrapper selector? Yep, we’d have some CSS leaking causing all the buttons in the application to have that styling, which is very likely what we don’t want. This has unfortunately tripped us up a few times in the past, resulting in very unfortunate styling in places we might not expect. We needed something new.

The Solution

As many of you probably know, there are quite a few options available when it comes to styling react components. We first decided we wanted to go with a CSS-in-JS approach for a few key reasons:

  • Local Scoping – This is the crux of our problem as described above. By default, CSS is applied globally.
  • Encapsulation – We’d be moving all the relevant information about a component into one file. This lowers the risk of forgetting to update a specific file that pertains to the component.
  • Portability – Similar to the point above, by having everything about a component in one file, it can easily be moved around.
  • Reusability – We could create shared styling ‘objects’ and reuse them in different components safely.
  • Dynamic Functionality – By rendering the CSS with our JS, we can easily incorporate props that might be passed in from parent components to dictate our styling.

Once we decided CSS-in-JS was the way to go, we landed on React-JSS as the package we would use. There are a few options here that all have different, subtle benefits so I’d encourage you to evaluate a few before landing on one.

Below is an example of a component using React-JSS.

import React from 'react';
import classnames from 'classnames';
import colors from 'constants/jssStyle/colors';
import { createUseStyles } from 'react-jss';

const feedbackMessageTypes = Object.freeze({
 SUCCESS: 'success',
 ERROR: 'error',
});

const useStyles = createUseStyles(() => ({
 message: {
   width: '500px',
   float: 'right',
   border: `1px solid ${colors.midGray}`,
   borderRadius: '4px',
 },
 title: (props) => ({
   backgroundColor:
     props.type === feedbackMessageTypes.SUCCESS ? colors.green : colors.red,
   color: colors.white,
   padding: '10px',
   fontSize: '16px',
 }),
 text: {
   padding: '10px',
   fontSize: '14px',
 },
}));

const FeedbackMessage = (props) => {
 const classes = useStyles({ type: props.type });

 const { text, title, setText, setTitle } = props;

 return (
   <span className={classes.message}>
     <div className={classes.title}>
       {title}
     </div>
       <div className={classes.text}>{text}</div>
   </span>
 );
};

export default FeedbackMessage;

There are a few things I want to unpack here. First, let’s focus on the actual style declaration.

const useStyles = createUseStyles(() => ({
 message: {
   width: '500px',
   float: 'right',
   border: `1px solid ${colors.midGray}`,
   borderRadius: '4px',
 },
 title: (props) => ({
   backgroundColor: props.type === feedbackMessageTypes.SUCCESS ? colors.green : colors.red,
   color: colors.white,
   padding: '10px',
   fontSize: '16px',
 }),
 text: {
   padding: '10px',
   fontSize: '14px',
 },
}));

As you can see above, we’re defining 3 different classes: message, title, and text. We don’t need these class names to be unique, as react-JSS will handle that on render. All we need to do is define our styling as an object and pass it in as the createUseStyles function.

You might also notice we have a few variables playing parts here. We’ve imported a standard colors utility file so that we can use the colors defined by the application.

We also are passing in props to determine the background color for our component. This allows the component to render it’s styling differently based on the props that we might pass in.

Once we get to actually defining the component, we see this line:

const classes = useStyles({ type: props.type });

This takes our styles that we’ve defined above and converts them to a hash of classes that we can apply to our component jsx.

And then finally we actually write the component JSX:

return (
   <span className={classes.message}>
     <div className={classes.title}>
       {title}
     </div>
     <div className={classes.text}>{text}</div>
   </span>
);

All we have to do now is write the jsx and include the classes we’ve defined in the correct place. For a more detailed tutorial, I recommend the official JSS documentation.

Moving Forward

Rather than do a huge sweeping change to implement React-JSS everywhere, we decided to implement it as we go. Essentially, if you’re writing a new component, React-JSS should be used and if you’re touching an old component’s styling, the CSS should be converted to use React-JSS. This allows us to move forward with a new CSS framework quickly, using agile methodologies, by updating pages to the new framework as we inevitably iterate on existing screens.

(Feature photo by Denise Johnson on Unsplash)

Posted By