How can I access to the themeProvider of styled components into a Portal in React?

174 Views Asked by At

I set up the theme provider from styled component, then I created a portal, and I elements from my portal cant access to the provider

This is my provider:

<ConfigProvider>
  <ThemeProvider theme={myTheme}> //<-- provider from styled-components
    <App />
  </ThemeProvider>
</ConfigProvider>

This is how I created the portal:

export const SetSelector = ()=>{
  const OptionsModal = useMemo(() => {
      return (
          <Wrapper $theme={configState.theme}>
            <Modal $theme={configState.theme}>
              // more components here, like buttons
            </Modal>
          </Wrapper>
      );
    }, [ . . . ]);
  
    return (
      <ListSelectorWrapper>
        {displayOptions && createPortal(OptionsModal, document.body)}
      </ListSelectorWrapper>
    );
  };
}

My components into my modal can't access to the theme provider from Styled Components, I got this error: Uncaught TypeError: Cannot read properties of undefined (reading '300') which is from a property from my Button:

background: ${props=>props.$variant==="flat"?"transparent":props.$theme==="dark"?props.theme.gray[300]:props.theme.gray[600]};

Also, I tried Wrapping in a provider my portal but is not working, I got the same error

const OptionsModal = useMemo(() => {
    return (
      <ThemeProvider theme={myTheme}>
        <WrapperOverlay $theme={configState.theme}>
          <Modal $theme={configState.theme}>
             // more components here, like buttons
          </Modal>
        </WrapperOverlay>
      </ThemeProvider>
    );
  }, [...]);

How Can I access to theme provider values into my portal component?

Solved: was my mistake

I was not accessing in correct way to the values: props.theme.colors.gray[300] instead props.theme.gray[300]

1

There are 1 best solutions below

1
On

My way of using Styled-Components.

  1. Create a ThemeContext

// ./context/ThemeContext.jsx

import { useState, createContext } from 'react'

export const ThemeContext = createContext()

export const ThemeProvider = ({ children }) => {
    const [theme, setTheme] = useState('light')

    return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
            {children}
        </ThemeContext.Provider>
    )
}
  1. Create a custom hook : "useTheme".

// ./hooks/useTheme.jsx

import { useContext } from "react"
import { ThemeContext } from "../context/ThemeContext"

export function useTheme() {
    const { theme, setTheme } = useContext(ThemeContext);

    const toggleTheme = () => {
        setTheme(t => t === 'light' ? 'dark' : 'light')
    }

    return { theme, toggleTheme }
}
  1. Create ThemeButton.

// ./components/ThemeButton.jsx

import { useTheme } from '../hooks/useTheme';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faLightbulb, faMoon } from '@fortawesome/free-regular-svg-icons';

// create the ButtonWrapper component
// retrieve data from the mode property
// change color when the darkmode is active

const ButtonWrapper = styled.button`
    color: ${(props) => props.mode === 'dark' && 'white'};
    background-color: transparent;
    border: none;
`

const ThemeButton = () => {
    // use custom hook
    const { theme, toggleTheme } = useTheme()

    return (
        // add "mode" props  to the component
        // that will have the value "theme"

        <ButtonWrapper
            type="button"
            mode={theme}
            onClick={() => toggleTheme()}
        >
            {theme === 'light' ? (
                <FontAwesomeIcon icon={faMoon} />
            ) : (
                <FontAwesomeIcon icon={faLightbulb} />
            )}
        </ButtonWrapper>
    );
}

export default ThemeButton;
  1. Create StyledProvider

I use the ThemeProvider from Styled-Components as I would use the style variables with SASS.

// ./context/StyledContext.jsx

import { ThemeProvider } from 'styled-components';

const styled = {
    colors: {
        black: "black",
        white: "white",
    },
}

export const StyledProvider = ({ children }) => {
    return <ThemeProvider theme={styled}>{children}</ThemeProvider>;
}

  1. create GlobalStyle

// GlobalStyle.jsx

import { createGlobalStyle } from "styled-components";
import { useTheme } from "./hooks/useTheme";

// {props => props.mode} to retrieve data from the mode property
// {props => props.theme} to retrieve data from StyledProvider

const GlobalStyleComponent = createGlobalStyle`
    body {
        background-color: ${props => props.mode === "dark" ? props.theme.colors.black : props.theme.colors.white };
        transition-duration: 200ms;
    }
`;

export const GlobalStyle = () => {
    const { theme } = useTheme()
    return <GlobalStyleComponent mode={theme} />
}
  1. Wrap your App with StyledProvider and ThemeProvider.

// index.jsx
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import { StyledProvider } from './context/StyledContext';
import { ThemeProvider } from './context/ThemeContext';
import { GlobalStyle } from './GlobalStyle';

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
    <StrictMode>
        <StyledProvider>
          <ThemeProvider>
              <GlobalStyle/>
             <App>
          </ThemeProvider>
        </StyledProvider>
    </StrictMode>
)