DISCOVERY

June 30th, 2021

Styling React Components With JSS

JSS

React

JavaScript

CSS

Sass

In my previous article on JSS, I discussed the improvements JSS makes over traditional CSS stylesheets and CSS preprocessors such as Sass. JSS utilizes the highly expressive JavaScript language, enables style reusability, dynamic styling, and provides name conflict resolution. Although JSS works with any front-end framework or library, it really shines when paired with React. In this article, I begin by discussing the basics of JSS in React applications. Then, I show sample code from my SaintsXCTF application, which is running in production and utilizes JSS for its stylesheets.

To use JSS in React applications, the react-jss npm module is used instead of the jss and jss-preset-default modules used in my prior article. Behind the scenes, React JSS uses both these modules, but exposes a React hook API for application code to use1.

The main exported method of the React JSS library is createUseStyles(), which takes a JavaScript object representing JSS styles as an argument and returns a React hook for use in components. This React hook, commonly named useStyles(), returns a classes object which can be attached to JSX/HTML elements as their class attribute.

For example, my SaintsXCTF application has a basic CheckBox component represented by the following function:

import React from 'react'; import { createUseStyles } from 'react-jss'; import styles from './styles'; const useStyles = createUseStyles(styles); const CheckBox: React.FunctionComponent<Props> = ({ id, checked, onChange, className }) => { const classes = useStyles(); return ( <div className={classes.checkBox} onClick={onChange}> <input type="checkbox" id={id} className={classes.input} checked={checked} /> <span>{checked && <p>N</p>}</span> </div> ); }; export default CheckBox;

I simplified this code a bit to focus on the parts that utilize JSS. The code starts by importing the createUseStyles() function from the React JSS library with the line import { createUseStyles } from 'react-jss'. createUseStyles() is used outside of the component function, taking a JSS styles object as an argument and returning a React hook definition, in my case named useStyles. The first line of the component function, const classes = useStyles(), invokes the React hook to retrieve all the stylesheet classes. The JSS classes are properties on the classes object. In the components return statement, JSS classes are passed to JSX elements via the className prop. For example, the <div> element's className prop is passed the checkBox JSS class.

In this code sample, the styles object passed as an argument to createUseStyles() is imported from a separate file. I generally follow this practice to logically separate component code from stylesheet code. The styles object, located in a styles.ts file, has the following code:

import Colors from '../../../styles/colors'; import { FontMixins } from '../../../styles/mixins'; export default { checkBox: { display: 'flex', cursor: 'pointer' }, input: { display: 'none', '& + span': { ...FontMixins.elegantIcons(), display: 'inline-block', border: '2px solid #999', borderRadius: 2, width: 16, height: 16, position: 'relative', '& > p': { color: '#FFF', position: 'absolute', margin: 0, top: -1, left: -3 } }, '&:checked + span': { backgroundColor: Colors.sxctfRed, border: `2px solid ${Colors.sxctfRed}` } } };

At a basic level, this object has two properties representing two classes: checkBox and input. These properties are objects themselves, containing CSS styles as properties. I'm also importing additional code for reusability purposes, which I will discuss next.

In this section, I discuss different approaches for creating reusable and dynamic stylesheet code with JSS. All of the code examples shown come from my SaintsXCTF application, with the front-end source code maintained in my saints-xctf-web repository.

In web applications, the same color schemes are often used throughout the UI components. In traditional CSS (before variables were added to the specification), these colors were copy-pasted throughout the stylesheets whenever they were needed. Maintaining colors in a singular location and referencing them in stylesheets is a better approach for writing clean code and easing future refactoring.

In my application, I maintain a colors.ts file which contains all the commonly reused colors.

const Colors = { sxctfRed: '#990000', spotPaletteBrown: '#a96a5b', spotPaletteCream: '#ffe6d9', spotPaletteBlue: '#58b7d2', statusSuccess: '#28a745', statusWarning: '#ffc107', statusFailure: '#dc3545', lightestBackground: '#fdfdfd', lightBackground: '#f5f5f5' }; export const FeelColors = [ '#EA9999', '#FFAD99', '#EAC199', '#FFD699', '#FFFFAD', '#E3E3E3', '#C7F599', '#99D699', '#99C199', '#A3A3FF' ]; export default Colors;

By default this file exports the Colors object, which contains properties with hex color codes. There is also a FeelColors list, which is used for application specific logic dealing with how user's felt on their exercises. FeelColors will look familiar if you read my prior article on JSS.

In component stylesheets, the Colors object is simply imported and its properties are referenced. A simplified example is shown below, with a container class holding a single backgroundColor style.

import Colors from '../../../styles/colors'; export default { container: { backgroundColor: Colors.lightBackground } };

In some cases, there are groups of styles that are reused in multiple stylesheets. In Sass these groups of styles are called mixins, since they can be “mixed in” with other styles. In my JSS code, I followed the same naming convention. My application has a mixins.ts file with all the style mixins. Mixins are simply JavaScript functions that return an object with CSS styles as properties.

For example, one of my mixins blueLink() is reused for many of the text links on the website.

class Mixins { static blueLink = (): object => ({ ...FontMixins.robotoBold(), fontSize: 14, textDecoration: 'none', color: Colors.spotPaletteBlue, cursor: 'pointer', '&:hover': { textDecoration: 'underline' } }); }

These mixins are “mixed in” to JSS classes by importing the Mixin class and using the spread notation on the mixin function. The following code mixes in blueLink() with the link JSS class.

import Mixins from './styles/mixins'; export default { link: { ...Mixins.blueLink(), marginTop: 15 } }

Sometimes multiple components contain matching groups of classes. In terms of reusability, this pattern goes above the scope of mixins. In my code I call this pattern “modules”. While mixins return an object with styles in them, modules return an object with JSS classes. All the modules in my application exist in a modules.ts file.

One of the modules is for exercise type filters in my application. I noticed that some of my components had identical classes and styles, but the logic these components held was different. Because of this, I kept the components separate but made a reusable module for the styles. The module is a function that returns a JSS styles object.

import { FontMixins } from './mixins'; import { Styles } from 'react-jss'; export class Modules { static filters = (): Styles< 'filters' | 'filterTitle' | '@media screen and (max-width: 900px)' | '@media screen and (max-width: 450px)' > => ({ filters: { display: 'flex', alignItems: 'center', justifyContent: 'center' }, filterTitle: { ...FontMixins.robotoSlab(), margin: '30px 40px 30px 0' }, '@media screen and (max-width: 900px)': { filterTitle: { margin: '20px 20px 20px 0' } }, '@media screen and (max-width: 450px)': { filterTitle: { display: 'none' } } }); }

This module is used with the JavaScript spread notation, similar to the mixins.

import { Modules } from '../../../styles/modules'; export default { ...Modules.membershipModal() };

The greatest thing about JSS is the ability to pass application state to stylesheets. This allows for dynamic styles that can change along with the application state. As a basic example, let's look at my applications Alert component. Alert displays messages designed to get the users attention. Alert messages can exist in four different states: error, warning, info, and success. The alert message shown below is in a success state.

The Alert component picks its state depending on a type prop.

import React, { ReactNode } from 'react'; import { createUseStyles } from 'react-jss'; import styles from './styles'; export type AlertType = 'error' | 'warning' | 'info' | 'success'; interface Props { message: ReactNode; type: AlertType; closeable: boolean; onClose?: () => void; } const useStyles = createUseStyles(styles); const Alert: React.FunctionComponent<Props> = ({ message, type, closeable, onClose }) => { const classes = useStyles({ type }); let alertIcon; switch (type) { case 'error': alertIcon = '\ue062'; break; case 'info': alertIcon = '\ue064'; break; case 'warning': alertIcon = '\ue063'; break; case 'success': alertIcon = '\ue052'; break; default: alertIcon = '\ue062'; } return ( <div className={classes.alert} data-cypress="alert"> <p className={classes.alertIcon}>{alertIcon}</p> <div className={classes.message}>{message}</div> {closeable && ( <p className={classes.closeIcon} data-cypress="alertCloseIcon" onClick={onClose}> M </p> )} </div> ); }; export default Alert;

Notice that the type prop is passed into the useStyles() React hook with const classes = useStyles({ type }). One or more props or state variables can be passed to useStyles() this way. In the stylesheet object for Alert, the type prop is accessible to CSS styles using the arrow function syntax.

import { AlertType } from './Alert'; import color from 'color'; import Colors from '../../../styles/colors'; import { FontMixins } from '../../../styles/mixins'; export default { alert: { display: 'flex', alignItems: 'center', padding: '10px 0', borderRadius: 3, backgroundColor: ({ type }: { type: AlertType }): string => type === 'warning' ? color(Colors.statusWarning).lighten(0.65).hex() : type === 'info' ? color(Colors.spotPaletteBlue).lighten(0.65).hex() : type === 'success' ? color(Colors.statusSuccess).lighten(1.1).hex() : color(Colors.statusFailure).lighten(0.65).hex() }, alertIcon: { ...FontMixins.elegantIcons(), fontSize: 28, margin: '10px 25px', color: ({ type }: { type: AlertType }): string => type === 'warning' ? Colors.statusWarning : type === 'info' ? Colors.spotPaletteBlue : type === 'success' ? Colors.statusSuccess : Colors.statusFailure }, message: { ...FontMixins.roboto(), fontSize: 16, margin: '5px 10px 5px 0' }, closeIcon: { ...FontMixins.elegantIcons(), fontSize: 24, margin: '0 10px 0 auto', cursor: 'pointer' } };

backgroundColor in the alert class and color in the alertIcon class utilize type for dynamic styling.

For more examples of components using dynamic styling in JSS, check out ExerciseLog, ProgressBar, and StepSlider.

JSS shines brightest when used with React, enabling dynamic styling for components, reusability, and modularization. In this article I demonstrated some approaches I took while creating reusable JSS code. However, your approach to JSS can be as wide as the JavaScript language itself. All the JSS code shown in this article and more is available in my saints-xctf-web repository.

[1] "JSS integration with React", https://cssinjs.org/react-jss/?v=v10.6.0