Sharing Variables between Stylus and React

Jonathan Mares
Qbits
Published in
6 min readFeb 21, 2018

--

At Quorum, we have been using Stylus as our styling preprocessor and have generally been pleased with the framework. However, our team has identified a few shortcomings over extended use. One such deficiency is the ability to import stylus into our React components and change styles with JavaScript code. Recently, I ran into this limitation while working on a small styling improvement.

I work on our spreadsheets-in-the-browser feature which we call Sheets. Sheets has both an outward facing page (accessible to anyone), and an internal page, which is accessible to Quorum’s clients. One of the use cases of the feature is to create a public facing spreadsheet of officials with columns such as party, religion, and ethnicity:

Sheet of all of the House of Representatives, sorting by Member Age

Sheets lets you sort and filter on many different pieces of information. Here’s how its done:

Hover over the triangle to expose options to sort and filter

In the internal application, you can also filter and sort, but hovering over the triangle exposes additional options such as adding and removing columns (represented by the minus and plus icons).

Sorting in the non-public main application

Since the main application supports three additional actions, we need to render three more icons in each column, and provide the wrapping container more space. Let’s take a look at an overview of the problem and how we went about solving it.

The Problem

Here’s what the icons looked like on the public spreadsheet initially:

Width of triangle overlay works for five icons, but is too wide when there are only two

Public facing spreadsheets only have the option to sort and filter by a column, as opposed to only being able to add or remove columns in the internal application. Let’s take a look at some of the code:

Contents of React Component, simplified

Each of canHideColumns, canSort, canSearch, canRemoveColumns, and canAddColumns are booleans that determine whether to render each icon. In the main application, most of the time each of the booleans are true, while for public spreadsheets only canSearch and canSort are true.

Here’s some of the styles for the class column-icons:

We also apply a style to pull the overlay to the right, effectively hiding it until the user hovers over the triangle:

.column-options // style applied on parent html element    right -1 * TotalColumnIconsWidth

The problem is that the triangle overlay is too wide when there are less than five icons, and would look cleaner if it was able to know how far to come out based on the number of buttons. Additionally, the logic to figure out which buttons to show is handled in React (specifically, the selectors that provide props to this component). As a result, the styling must be applied dynamically when rendering the component.

It would be great to change the 5 in the stylus calculation to be the number of buttons we render (any value between 0 and 5, for example)— but unfortunately we can’t change the style rule on the fly. What are our options?

A Partial Solution

One partial solution to this problem is to conditionally apply a style rule based on a JavaScript variable. The excellent classnames utility makes it easy to conditionally apply classes to components. Here’s an example in action:

Use of classNames library

Here, if the boolean canRenameColumns is true, we apply the column-name-pointer style to add a cursor: pointer attribute to the div.

We could have 5 separate styles — something like column-icons-1, column-icons-2, column-icons-3, etc, where the number represents the number of icons we want to render. If we compute the number of buttons we need to render and feed that information into classNames, we’ll be able to conditionally render the right class, given the number of icons. We can modify our render method to reflect this idea:

A fancier version results in cleaner JSX:

That actually doesn’t look all too bad. However, we will still need 5 separate styles for each case. There’s a better way.

We can instead compute the styles directly in the component, passing in the styling for the component via a styles prop. This is the way our React Native app is styled, because React Native does not support CSS. However, we have many styles and variables in stylus files that would be great to reuse. In an attempt to reuse Stylus code, we discovered that Stylus supports importing variables from a .json file. Webpack (a module loader/bundler we use extensively) also supports this type of file, so we were in business to start sharing variables! Here are the steps to make this work, using our column buttons example

Step 1: Create a JSON file

We are going to first create a .json file in the same directory as your .styl
file. Here’s an example:

Constants file we will import

Remember that JSON files do not support a trailing comma.

Step 2: Import your JSON file from stylus

In your stylus file (this is tested with the json file in the same directory), import your file:

json(“sheets-variables.json”)
// — — -BEGIN json variable imports — — — — — — — //
ColumnIconsSize = columnIconsSize
ColumnIconsMargin = columnIconsMargin
// — — -END json variable imports — — — — — — — //
… // rest of your stylus contents

These variables can now be freely used in your stylus file. Our convention for stylus variables is to start variables with a capital letter, and this is convenient for us here because it is nice to indicate that we are importing variables from a file. Without this, it may not be obvious that columnIconsSize is being imported.

Step 3: Importing your JSON file in React

Let’s move to JS world. Here, we will pull in the variables from the json file and decode them for use in JavaScript.

// your js file/component here
// … other imports
import { decodeStyles, encodeStyles } from “imports/helperFunctions”
// styles
import sheetVars from “sheets-variables.json”
const sheetsVariables = decodeStyles(sheetVars)

decodeStyleswill convert stringified pixel values to integers and ignores styles without ‘px’ or spaces. Stylus needs them in this format, and because JS is more flexible, the parsing work falls in JS world. Here’s an implementation of decodeStyles that we currently use:

This decode function isn’t comprehensive, nor does it support nesting, but it will be sufficient for our current needs.

Step 4: Calculate styles

Now, we can specify the logic for the styles:

We also need to encode the styles back with `encodeStyles`, since the browser needs the styles back in the original stringified format. Here’s an implementation of encodeStyles:

Last Step: Pass in Styles

Pass in styles via the `style` prop

Final Thoughts

If you use Stylus but aren’t ready to make the jump to writing CSS exclusively in your React components, importing a JSON file with constants may be a good solution in the meantime. If you found this solution useful, let us know in the comments below!

At Quorum, we rely heavily on open source tools but aren’t afraid to explore and find creative solutions to problems in software engineering. Interested in what we’re doing? We’re hiring!

You can also get in touch with me on Twitter at @jmares93

--

--