'Typechecking React library with Styled Components

I'm currently working in a design system library to test some things. Basically, the library is a Styled Component wrapper in order to create themes. I built this library with Typescript, React, Styled Components and Rollup as bundler.

The problem is when the user use this library, the vscode doesn't helps with the typechecking for the theme declarations.

This is the index.js from the library. Basically is a HOC in which we have a context and a ThemeProvider HOC from Styled components.

import { createContext, useState } from "react";
import "styled-components";
import {
  DefaultTheme,
  ThemeProvider as SCThemeProvider,
} from "styled-components";
import { themes } from "./design-system/colorTheme";
import React from "react";

interface ThemeContextAPI {
  toggleTheme: () => void;
  currentTheme: DefaultTheme;
}

const ThemeContext = createContext<ThemeContextAPI | null>(null);

const ThemeProvider: React.FC<{ children?: React.ReactNode }> = ({
  children,
}) => {
  const [currentTheme, setCurrentTheme] = useState<DefaultTheme>(themes.light);

  const toggleTheme = () => {
    setCurrentTheme((prev) =>
      prev === themes.light ? themes.dark : themes.light
    );
  };

  const values: ThemeContextAPI = {
    toggleTheme,
    currentTheme,
  };

  return (
    <ThemeContext.Provider value={values}>
      <SCThemeProvider theme={currentTheme}>{children}</SCThemeProvider>
    </ThemeContext.Provider>
  );
};

export default ThemeProvider;
export { ThemeContext };

As we see, we have a SCThemeProvider that is a ThemeProvider component from Styled component. This brings to the user, use all the theme variables inside all the styled components. The theme that this component provides is extended as the SC documentation says, so DefaultTheme is declared like this:

export interface Theme {
  primary?: Color;
  primaryContainer?: Color;
  secondary?: Color;
  secondaryContainer?: Color;
  tertiary?: Color;
  tertiaryContainer?: Color;
  cuaternary?: Color;
  cuaternaryContainer?: Color;
  // -------------
  surface?: Color;
  surfaceVariant?: Color;
  background?: Color;
  error?: Color;
  errorContainer?: Color;
  // -------------
  onPrimary?: Color;
  onPrimaryContainer?: Color;
  onSecondary?: Color;
  onSecondaryContainer?: Color;
  onTertiary?: Color;
  onTertiaryContainer?: Color;
  onCuaternary?: Color;
  onCuaternaryContainer?: Color;
  // --------------
  onSurface?: Color;
  onSurfaceVariant?: Color;
  onError?: Color;
  onErrorContainer?: Color;
  onBackground?: Color;
  outline?: Color;
  // --------------
  inverseSurface?: Color;
  inverseOnSurface?: Color;
  inversePrimary?: Color;
  colorPalette: ColorPalette<ColorSet>;
  surfaceTones: SurfaceTones;
}

declare module "styled-components" {
  export interface DefaultTheme extends Theme {}
}

The library is consumed like this, (The example is written in next.js):

import { GlobalStyle, ThemeProvider } from "rakes-design-sys";

function MyApp({ Component, pageProps }) {
  return (
    <>
      <ThemeProvider>
        <GlobalStyle />
        <Component {...pageProps} />
      </ThemeProvider>
    </>
  );
}

export default MyApp;

But when I use the theme inside a styled component, the vscode doesn´t shows the types of the theme. I´ve already configured TS config to create declarations files but still doesn't work. I just put this in tsconfig and rollupconfig

    "declaration": true,
    "declarationDir": "types",
    "emitDeclarationOnly": true

And I use the rollup-plugin-dts like this in my rollup configuration

 {
    input: "dist/types/index.d.ts",
    output: [{ file: "dist/index.d.ts", format: "es" }],
    plugins: [dts()],
  },

This generates a folder inside dist called types in which is contained all my files in the library but with the .d.ts extensions and creates another index.d.ts inside dist.

By this way typechecking works when I use the context like:

import { ThemeContext } from "rakes-design-sys";
import { useContext, useEffect } from "react";
import { OHeader } from "../styles/organisms/OHeader.styles";

const App = () => {
  const { toggleTheme, currentTheme } = useContext(ThemeContext);
  console.log(currentTheme.primaryContainer);
  return <OHeader onClick={() => toggleTheme()}>HelloWorld</OHeader>;
};

export default App;

In the console.log(currentTheme.something) the typechecking is working but this doesn´t work inside a styledComponent like this

import styled from "styled-components";

export const OHeader = styled.div`
  background-color: ${(props) => props.theme.primary};
`;

It's already working, but when I write props.theme.something, it doesn't predict or typechecking the values inside the current theme that the styled components is providing.



Solution 1:[1]

The provider context for styled-components acts as a singleton. So, when you import from styled-components in your library and in your consumer app, the theme context doesn't match.

To fix make styled-components a peer dependency in your library.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 mhatch