Adding themes to Next.js with styled-components, mobx, and typescript
Tags: nextjs, tutorial, typescript
Published: 2022-04-15 22:33:45

## Dark Mode vs Light Mode?
Let your users decide. Provide the option to switch seamlessly between themes with `styled-components` and `mobx`.

## Getting Started
1. Installing dependencies
2. Creating the theme constants
3. Configuring styled-components with Next.js
4. Adding styled-components' server-side functionality
5. Persisting data
6. Creating a mobx store
7. Create a global style
8. Creating the StyleProvider
9. Adding types to the theme 
10. Making sure it works


### 1. Install
- The dependencies: `yarn add styled-components mobx mobx-react`
- The dev-dependencies: `yarn add -D @types/styled-components`

### 2. Create a file for the theme constants
`themes.constants.ts`
In this file, we're going to define the themes and other relavant constants.

- Colours
```
const COLOURS = {
  black: '#000000',
  white: '#FFFFFF'
}
```

- Dark theme
```
const DARK_THEME = {
  name: 'dark',
  background: COLOURS.black,
  textPrimary: COLOURS.white
}
```

- Light theme
```
const LIGHT_THEME = {
  name: 'light',
  background: COLOURS.white,
  textPrimary: COLOURS.black
}
```

- Export the Default theme
```
export const DEFAULT_THEME = 'dark'
```

- Export the themes
```
export const THEMES = {
  dark: DARK_THEME,
  light: LIGHT_THEME,
}
```

## 3. Add styled-components to the `next.config.js` file
In the nextConfig object, add the key-pair values:
```
  compiler: {
    styledComponents: true,
  },
```

### 4. Add `ServerStyleSheet` to `_document.tsx`
Add the `getInitialProps` method to the `MyDocument` class shown below.

_If you haven't already created `_document.tsx`, add it to your `pages` folder. And paste the following:_

```
import Document, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentContext,
} from 'next/document';
import { ServerStyleSheet } from 'styled-components';

class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            sheet.collectStyles(<App {...props} />),
        });

      const initialProps = await Document.getInitialProps(ctx);
      return {
        ...initialProps,
        styles: [
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>,
        ],
      };
    } finally {
      sheet.seal();
    }
  }
  render() {
    return (
      <Html>
        <Head>
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

```

### 5. Create utility functions to allow persisting data with localStorage
`utils.ts`

```
export const setStorage = (key: string, value: unknown) => {
  window.localStorage.setItem(key, JSON.stringify(value));
};
export const getStorage = (key: string) => {
  const value = window.localStorage.getItem(key);

  if (value) {
    return JSON.parse(value);
  }
};

```

### 6. Create a UI Store with `mobx`
`uiStore.ts`

```
import { action, makeAutoObservable, observable } from 'mobx';
import { setStorage } from './utils';

type Themes = 'dark' | 'light';

class UiStore {
  @observable
  private _theme: Themes = 'light';

  @observable
  private _initializing: boolean = true;

  constructor() {
    makeAutoObservable(this);
  }

  get theme() {
    return this._theme;
  }
  get initializing() {
    return this._initializing;
  }

  @action
  toggleTheme() {
    this._theme = this._theme === 'light' ? 'dark' : 'light';
    setStorage('theme', this._theme);
  }

  @action
  changeTheme(nameOfTheme: Themes) {
    this._theme = nameOfTheme;
  }

  @action
  finishInitializing() {
    this._initializing = false;
  }
}

export const uiStore = new UiStore();

```

### 7. Create a global style with `styled-components`
We'll soon be able to access the theme in the styled-components in the following manner.
`global-styles.ts`
```
export const GlobalStyle = createGlobalStyle`
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }

html {
  background: ${({ theme }) => theme.colors.body};
}
  
`;
```

### 8. Creating the StyleProvider
`style-provider.tsx`

```
import { observer } from 'mobx-react';
import { useEffect, ReactNode } from 'react';
import { ThemeProvider } from 'styled-components';
import { uiStore } from './uiStore';
import { DEFAULT_THEME,  THEMES } from './theme.constants';
import { GlobalStyle } from './global-styles`
import { getStorage, setStorage } from './utils';

interface OnlyChildren {
  children: ReactNode
}

const StyleProviderComponent = (props: OnlyChildren) => {
  const { children } = props;
  const { theme } = uiStore;

  useEffect(() => {
    if (!getStorage('theme')) {
      setStorage('theme', DEFAULT_THEME);
    }
    const storageThemeName = getStorage('theme');
    uiStore.changeTheme(storageThemeName);
    uiStore.finishInitializing();
  }, []);

  return (
    <ThemeProvider theme={THEMES[theme]}>
      <GlobalStyle />
      {children}
    </ThemeProvider>
  );
};

export const StyleProvider = observer(StyleProviderComponent);
```

### 9. Add typing to the theme
`default-theme.d.ts`

```
import 'styled-components';

declare module 'styled-components' {
  export interface DefaultTheme {
    name: string;
    colors: {
      primary: string;
      textPrimary: string;
    };
  }
}
```

### 10. Wrap the provider around `pages/_app.tsx`

```
import type { AppProps } from 'next/app';
import { StyleProvider } from './style-provider';

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <StyleProvider>
      <Component {...pageProps} />
    </StyleProvider>
  );
}

export default MyApp;
```

### Test that it works in `pages/index.tsx`

```
import type { NextPage } from 'next';
import Head from 'next/head';
import { uiStore } from './uiStore';

const Home: NextPage = () => {
  const { initializing } = uiStore;

  if (!initializing) {
    return <h1>Loading...</h1>;
  }

  return (
    <>
      <Head>
        <title>
          Adding themes to Next.js with styled-components, mobx, and typescript
        </title>
        <meta name='description' content='Tutorial by Zach Nugent' />
        <link rel='icon' href='/favicon.ico' />
      </Head>
      <button onClick={() => uiStore.toggleTheme()}>Switch theme</button>
    </>
  )
}
export default Home

```

You can access the theme in styled-components by adding a callback function (ex: `${({ theme }) => theme.colors.textPrimary}`) in between the back ticks.

Example:

```
const Button = styled.button`
  background: ${({ theme }) => theme.colors.body};
  border: 1px solid ${({ theme }) => theme.colors.accent};
  color: ${({ theme }) => theme.colors.textPrimary};
`
```

Thanks for reading 🙂