Environment Based Themes with Dockerized React

Posted Friday, January 15, 2021.

In order to deploy this strategy in production, you will need to be able to inject runtime variables into your dockerized react apps, covered in a previous post. The goal of this task was to be able to set a single variable (for example REACT_APP_STYLE_CODE), and have that effect which classes were chosen in the application.

In order to achieve this, I used the following file structure:

.
├── src
│ ├── theme.scss
│ ├── index.scss
│ └── variables.scss
└── ...

Let's say we have Organization A (ORG_A) and Organization B (ORG_B). I defined a default set of stylings, and then an override for each company, should their styles differ. See an example below in variables.scss:

$defaults: (
"primary": #2cdc26,
"link_disabled": #6c757d,
);

$classes: (
"ORG_A": (
"primary": #dc5e26,
"link_disabled": #ce490b,
),
"ORG_B": (
"primary": #c8102e,
),
"DEF": $defaults,
);

In theme.scss is where the magic happens. In this case, I wanted to override some styles in a component library using react-bootstrap (see post about this library). In the theme.scss file, you can begin to define your styles. The map-merge function will merge each organizations style's against the default styles, to fill in any that are missing.

@use "sass:map";
@import "./variables.scss";

@each $class, $styles in $classes {
$merged: map-merge($defaults, $styles);

.btn-link-#{$class} {
color: map-get($merged, "primary");
}

.btn-link-#{$class}:disabled,
.btn-link-#{$class}.disabled {
color: map-get($merged, "link_disabled");
}
}

When this is compiled, it will loop through each of the companies (including default), and create classes for each. When they have their own colours they will use them, if the colours are missing they will fall back to the default.

.btn-link-ORG_A {
color: #dc5e26;
}
.btn-link-ORG_A.disabled,
.btn-link-ORG_A:disabled {
color: #ce490b;
}

.btn-link-ORG_B {
color: #c8102e;
}
.btn-link-ORG_B.disabled,
.btn-link-ORG_B:disabled {
color: #6c757d;
}

.btn-link-DEF {
color: #2cdc26;
}
.btn-link-DEF.disabled,
.btn-link-DEF:disabled {
color: #6c757d;
}

To get these styles into our app, import this theme file into our main index.scss file:

@import "./theme.scss";

Using The Classes

When we want to use these styles, we can just grab the style code from the environment and add them to our classes.

const org = process.env.REACT_APP_STYLE_CODE;
return <Button className={`btn-link-${org}`} variant="link" />;

If your environment set ORG_A, you would get btn-link-ORG_A as the class for this button, which would then map to:

.btn-link-ORG_A {
color: #dc5e26;
}

In Closing

This process seems hacky, but has worked well for me. A downside of this approach is you will have duplicated styles for each organization. If there is a better way to go about this, please let me know!


Tagged With: