DEV Community

Cover image for Stop using CSS, use JSS instead.
Anatolii
Anatolii

Posted on

Stop using CSS, use JSS instead.

Introduction

How you will make this with css?

Alt TextYou are not able to make it theoretically and practically with css.

This is one of the all reasons why CSS sucks versus JSS.
For sure, JSS have more functionality with React, but who in 2021 are using vanilla JS?

About JSS

JSS is an authoring tool for CSS which allows you to use JavaScript to describe styles in a declarative, conflict-free and reusable way. It can compile in the browser, server-side or at build time in Node.

JSS is framework agnostic. It consists of multiple packages: the core, plugins, framework integrations and others.

JSS features

  1. Real CSS.
  2. Collision-free selectors.
  3. Code reuse.
  4. Ease of removal and modification.
  5. Dynamic styles.
  6. User-controlled animations.
  7. Critical CSS.
  8. Plugins.
  9. Expressive syntax.
  10. Full isolation.
  11. React integration.
  12. JavaScript build pipeline.

Small project as example

Setuping Environment

Stack: Nextjs, Typescript, Jss

yarn create next-app --typescript
Enter fullscreen mode Exit fullscreen mode
yarn add react-jss jss-plugin-template jss-plugin-global jss-plugin-nested jss-plugin-camel-case jss-plugin-default-unit jss-plugin-compose
Enter fullscreen mode Exit fullscreen mode

Of course we need add JSS and some plugins.

So create file pages/_document.tsx (to set up ssr)

import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { SheetsRegistry, JssProvider, createGenerateId } from 'react-jss';

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang={'en'}>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

MyDocument.getInitialProps = async (ctx) => {
  const registry = new SheetsRegistry();
  const generateId = createGenerateId();
  const originalRenderPage = ctx.renderPage;
  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App) => (props) =>
        (
          <JssProvider registry={registry} generateId={generateId}>
            <App {...props} />
          </JssProvider>
        ),
    });
  const initialProps = await Document.getInitialProps(ctx);
  return {
    ...initialProps,
    styles: (
      <>
        {initialProps.styles}
        <style id={'server-side-styles'}>{registry.toString()}</style>
      </>
    ),
  };
};
Enter fullscreen mode Exit fullscreen mode

All what we do here its adding necessary JSS SheetsRegisty to our default ctx.renderPage manually so that the whole react tree gets the required stylesheet.
The following snippet shows the available option we can use on ctx.renderPage.

After that delete folder styles because now ̶y̶o̶u̶r̶ ̶l̶i̶f̶e̶ ̶b̶e̶c̶o̶m̶e̶s̶ ̶b̶e̶ ̶b̶e̶t̶t̶e̶r̶ we will not be need css more.

In pages/_app.tsx (this story just about example of jss, in real life dont use this archetecture, instead use state management util and split up your providers in different Layouts (you can read another story about some structure moments link ))

import type { AppProps } from 'next/app';
import { useState } from 'react';
import { ThemeProvider } from 'react-jss';

const _App = ({ Component, pageProps }: AppProps) => {
  const initialTheme = {
    background: '#222222',
    text: '#e7f1fe',
  };

  const [theme, setTheme] = useState(initialTheme);

  return (
    <ThemeProvider theme={theme}>
      <Component {...pageProps} setTheme={setTheme} />
    </ThemeProvider>
  );
};
export default _App;
Enter fullscreen mode Exit fullscreen mode

So here we wrapping <Component {...pageProps} setTheme={setTheme}/> with <ThemeProvider theme={theme}> and upper we initializate with hook useState [theme, setTheme] so then we need move to file pages/index.tsx

With ts, as we will receive props in index.tsx we need write type which will be describes which props we will receive

type ThemeType = { [Property in 'background' | 'text']: string };
type AppPropsType = {
 setTheme: Dispatch<SetStateAction<{ThemeType>> 
};
Enter fullscreen mode Exit fullscreen mode

and here we adding ThemeType.
Finally lets try to add styling with JSS, for do it we need

const useStyles = createUseStyles(({ background, text }: ThemeType) => ({}));
Enter fullscreen mode Exit fullscreen mode

so in first param we can get access of our theme properties and for better code lets give type for this params.
Then as returning value we will write styling code,
as we added jss-plugin-global we have opportunity to change gloabal styling, as example lets nullity default browser styles, to do that in returning object we need add key '@global' with value { body: {padding: 0,margin: 0,},},
as least in result we should have

const useStyles = createUseStyles(({ background, text }: ThemeType) => ({
  '@global': {
    body: {
      padding: 0,
      margin: 0,
    },
  },
}));
Enter fullscreen mode Exit fullscreen mode

then, lets add some class

  container: {
    background,
    color: text,
    width: '100vw',
    height: '100vh',
    font: { family: 'monospace', size: 20 },
  },
Enter fullscreen mode Exit fullscreen mode

as you see we don't need to write fonFamily or fontSize,
we can easyly structurate it with object with key font.
Then, in body of App component, we will use our useStyles by

 const { container } = useStyles();
 return <div className={container}>App</div>;
Enter fullscreen mode Exit fullscreen mode

and all code of this file

import { SetStateAction } from 'react';
import { Dispatch, FC } from 'react';
import { createUseStyles } from 'react-jss';

type ThemeType = { [Property in 'background' | 'text']: string };
type AppPropsType = { setTheme: Dispatch<SetStateAction<ThemeType>> };

const useStyles = createUseStyles(({ background, text }: ThemeType) => ({
  '@global': {
    body: {
      padding: 0,
      margin: 0,
    },
  },
  container: {
    background,
    color: text,
    width: '100vw',
    height: '100vh',
    font: { family: 'monospace', size: 20 },
  },
}));

const App: FC<AppPropsType> = () => {
  const { container } = useStyles();
  return <div className={container}>App</div>;
};
export default App;
Enter fullscreen mode Exit fullscreen mode

Finally lets test this part by command

yarn dev
Enter fullscreen mode Exit fullscreen mode

as we setup our theme, we should have (dark background, and white text color)
Alt Text

For sure, u easyly can make this with css, yeah, so right now will be advanced features
we can create

const CENTERED_FLEX_CONTAINER = 'centered-flex-container'
Enter fullscreen mode Exit fullscreen mode

so then we can use it as

  [CENTERED_FLEX_CONTAINER]: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
  },
Enter fullscreen mode Exit fullscreen mode

and then due to we added plugin jss-plugin-compose we can use in

container: {
    composes: [`$${CENTERED_FLEX_CONTAINER}`],
    //other code
},
Enter fullscreen mode Exit fullscreen mode

For see killer-feature we need to create function which will generate random color, function:

  const toGetRandomColor = () => `#${Math.random().toString(16).substr(-6)}`;
Enter fullscreen mode Exit fullscreen mode

and with hook useEffect make inverval function which in every iteration set new color

  const [color, setColor] = useState(theme.text);

  useEffect(() => {
    const interval = setInterval(() => {
      setColor(toGetRandomColor());
    }, 420);
    return () => clearInterval(interval);
  }, []);
Enter fullscreen mode Exit fullscreen mode

then we need paste our random color to useStyles

const { container } = useStyles({ color } as any);
Enter fullscreen mode Exit fullscreen mode

and in useStyles add new class

colorContainer: ({ color }: any) => ({ color })
Enter fullscreen mode Exit fullscreen mode

so at least in every 0.42 sec we will see updated class, so in dev tools you can see that its not an inline styling, class dynamicly changes value, that is absolute awesome, so then for test that our theme can be dynamiclty changed lets get the theme we can easyly do this with useTheme hook

After than, we need an array with theme keys so:

  const themeKeysArr = Object.keys(theme) as (keyof ThemeType)[];
Enter fullscreen mode Exit fullscreen mode

so then in jsx lets add simple inputs construction

 {themeKeysArr.map((name) => {
   return (
     <input value={theme[name]} placeholder={name.toUpperCase()} onChange={onChange} name={name} key={name} />
    );
  })}
Enter fullscreen mode Exit fullscreen mode

after that lets add some styling to inputs

  inputsContainer: {
    margin: [8, 0, 0, 0],
    padding: 10,
    '& input': {
      outline: 'none',
      border: '1px solid',
      borderRadius: 8,
      padding: [6, 8],
      margin: [0, 4],
      color: text,
      background: 'transparent',
    },
  },
Enter fullscreen mode Exit fullscreen mode

In JSS & have the same logic as have in Sass, also using [8, 0, 0, 0] we can setup (marginTop - 8, margin(right, vbottom, left) is equal to zero).
Then lets add class container with this styling:

  contentContainer: {
    composes: [`$${CENTERED_FLEX_CONTAINER}`],
    flex: { direction: 'column' },
  }
Enter fullscreen mode Exit fullscreen mode

Finally lets update our jsx part with this code:

    <div className={`${container} ${colorContainer}`}>
      <div className={contentContainer}>
        <div>STOP USE CSS, USE JSS INSTEAD.</div>
        <div className={inputsContainer}>
          {themeKeysArr.map((name) => {
            return (
              <input value={theme[name]} placeholder={name.toUpperCase()} onChange={onChange} name={name} key={name} />
            );
          })}
        </div>
      </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

for sure we need destructurate other classes with: const { container, contentContainer, inputsContainer, colorContainer } = useStyles({ color } as any); and to add multi classes we need use jsx (ES6 syntax) at least we should have something like that: final result

And final code:

import { ChangeEventHandler, SetStateAction, useEffect, useState } from 'react';
import { Dispatch, FC } from 'react';
import { createUseStyles, useTheme } from 'react-jss';

type ThemeType = { [Property in 'background' | 'text']: string };
type AppPropsType = { setTheme: Dispatch<SetStateAction<ThemeType>> };

const CENTERED_FLEX_CONTAINER = 'centered-flex-container';

const useStyles = createUseStyles(({ background, text }: ThemeType) => ({
  '@global': {
    body: {
      padding: 0,
      margin: 0,
    },
  },
  [CENTERED_FLEX_CONTAINER]: {
    display: 'flex',  <div className={`${container} ${colorContainer}`}>
      <div className={contentContainer}>
        <div>STOP USE CSS, USE JSS INSTEAD.</div>
        <div className={inputsContainer}>
          {themeKeysArr.map((name) => {
            return (
              <input value={theme[name]} placeholder={name.toUpperCase()} onChange={onChange} name={name} key={name} />
            );
          })}
        </div>
      </div>
    </div>
    alignItems: 'center',
    justifyContent: 'center',
  },

  container: {
    composes: `$${CENTERED_FLEX_CONTAINER}`,
    background,
    font: { family: 'monospace', size: 20 },
    width: '100vw',
    height: '100vh',
  },

  colorContainer: ({ color }: any) => ({ color }),

  contentContainer: {
    composes: [`$${CENTERED_FLEX_CONTAINER}`],
    flex: { direction: 'column' },
  },

  inputsContainer: {
    margin: [8, 0, 0, 0],
    padding: 10,
    '& input': {
      outline: 'none',
      border: '1px solid',
      borderRadius: 8,
      padding: [6, 8],
      margin: [0, 4],
      color: text,
      background: 'transparent',
    },
  },
}));

const App: FC<AppPropsType> = ({ setTheme }) => {
  const theme = useTheme<ThemeType>();
  const [color, setColor] = useState(theme.text);

  const toGetRandomColor = () => `#${Math.random().toString(16).substr(-6)}`;

  useEffect(() => {
    const interval = setInterval(() => {
      setColor(toGetRandomColor());
    }, 420);
    return () => clearInterval(interval);
  }, []);

  const { container, contentContainer, inputsContainer, colorContainer } = useStyles({ color } as any);

  const onChange: ChangeEventHandler<HTMLInputElement> = ({ target: { value, name } }) => {
    setTheme((state) => ({ ...state, [name]: value }));
  };
  const themeKeysArr = Object.keys(theme) as (keyof ThemeType)[];

  return (
    <div className={`${container} ${colorContainer}`}>
      <div className={contentContainer}>
        <div>STOP USE CSS, USE JSS INSTEAD.</div>
        <div className={inputsContainer}>
          {themeKeysArr.map((name) => {
            return (
              <input value={theme[name]} placeholder={name.toUpperCase()} onChange={onChange} name={name} key={name} />
            );
          })}
        </div>
      </div>
    </div>
  );
};
export default App;
Enter fullscreen mode Exit fullscreen mode

Conclusion

Its just a small part of all features of jss, but this small example can give you big opportunity and understating of jss.

Thanks for reading, I so appreciate this ♥.

Discussion (1)

Collapse
meleeman01 profile image
hatInTheCat

this is cool but. i could do the same thing with less code using web components and template css with no perf hit.