Friday, May 29, 2020

combining css modules, sass, and context providers for swappable themes with locally scoped css

Esay Silva's How to use Sass and CSS Modules with create-react-app does a great job of setting up Sass with create-react-app and then using them as CSS modules... node-sass does a good job of letting your create-react-app created react app use Sass - if you name a css file Foo.module.css or Foo.module.scss, it gets automagically locally scoped classnames, so your react code can be like

import BrandStyle from "./GreenCo/Theme.module.scss";

and then later 

<button className = {BrandStyle.btn}>Assuming .btn was the original class name</button>
The css sent to the browser will prefix .btn with a unique bit of randomness, so you can safely have lots of components with css code that refers to "btn" and they won't interfere with each other in the browser.

There's nothing too magic about that object you get from the import (the one I'm calling BrandStyle here) - it's just a map from the name as it appears in your original .scss or .css to the name it shows up as in the css that is sent to the browser. (And you can further refer to multiple class names, either with the lightweight classnames utility or just by gluing various parts together with spaces.)

But if you're thinking in terms of a customizable Component Library where you would like developers to be able to override styles of some basic components, but without passing in the overridden styles or CSS to every child, you can do it via context.

I was  rusty on React Contexts - at first I thought by wrapping a segment of JSX/Dom in a Context, each child would get new properties automagically... nope! "Context" is its own beast and doesn't use props to do its thing.

It seems best to make up a trivial custom Context object that can be referenced both by the parent / grandparent app or component as well as the children that will be reading from it...

ThemeContext.js:
import React from "react";
const ThemeContext = React.createContext();
export default ThemeContext;

and then use it as a Provider in the parent:

App.js
import BrandStyle from "./GreenCo/Theme.module.scss";
import ThemeContext from "./core-ux/ThemeContext.js";
//... 
      <ThemeContext.Provider value={{ brandstyle: BrandStyle }}>
        <header>
          <PushButton>Button by Theme....</PushButton>
        </header>
      </ThemeContext.Provider>

and then refer to it in the child:

PushButton.js:
import styles from "./PushButton.module.scss";

import ThemeContext from "./ThemeContext.js";

const PushButton = (props) => {
  const theme = React.useContext(ThemeContext);

  const comboStyle = `${styles.btn} ${theme.brandstyle.btn}`;

  return (
    <button
      className={comboStyle}
//...

So here we see how it can have its own local styles in PushButton.module.scss, grab an additional style from the ThemeContext, and then combine them in a string. (In the actual code I do more checking to make sure theme.brandstyle exists.)



No comments:

Post a Comment