Rémi Bernard
Rémi Bernard

Reputation: 119

React Context Provider override not working

The Goal

Hello 👋🏻

I have a React project with NextJS where I want to chose whether using light or dark theme depending on the background / section.

My solution

I have found that the best solution to my problem is using React Context API. To change the theme I just need to override the provider with a new one (as indicated in the documentation).

The problem

I have the start of a solution but when overriding the Provider, I noticed that the change isn't effective for every components even if in the devTools the context in theses components is the correct one. 😕

I have tried switching from useContext(context) to <context.Consumer> but there was no improvement.

The code:

/context/Theme

import { createContext, JSXElementConstructor, ReactElement, ReactFragment, ReactNode, ReactPortal } from "react";
import { getTheme, Theme } from "../constants/theme";

// type of getTheme: (theme: 'light' | 'dark') => Theme

const ThemeContext = createContext<Theme>(getTheme('light'))

export default ThemeContext

interface ThemeProviderProps {
    theme: 'light' | 'dark'
    children: string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | ReactPortal | null | undefined
}

interface ThemeConsumerProps {
    children: (value: Theme) => ReactNode
}

// The goal here is to make the theme switching more natural
export const ThemeProvider = (props: ThemeProviderProps): JSX.Element => {
    return (
        <ThemeContext.Provider value={getTheme(props.theme)}>
            {props.children}
        </ThemeContext.Provider>
    )
}

// Just for convenience
export const ThemeConsumer = (props: ThemeConsumerProps): JSX.Element => {
    return <ThemeContext.Consumer>{props.children}</ThemeContext.Consumer>
}

/pages/_app (Starting point of NextJS App, equivalent to /src/App

import '../styles/globals.css'
import type { AppProps } from 'next/app'

import { ThemeProvider } from "../context/Theme";

export default function App({ Component, pageProps }: AppProps) {
    return (
        <ThemeProvider theme="light">
            <Component {...pageProps} />
        </ThemeProvider>
    )
}

/common/Popup/Popup

import {ReactNode, useContext, useState} from "react";
import styles from "./Popup.module.css"

import Typography, {Typos} from "../Typography";
import Icon from "../Icon";

import {CloseIcon} from "../../icons";
import ThemeContext, {ThemeProvider, ThemeConsumer} from "../../context/Theme";

interface Props {
    children: ReactNode
    title: string
    open: boolean
    close: () => void
}

export default function (props: Props): JSX.Element {
    return (
        <>
            // ...
            <ThemeProvider theme="dark">
                <div className={styles.header}>
                    <Typography level={Typos.TITLE_2} bold>{ props.title }</Typography>  // The useContext works inside of this components
                    <ThemeConsumer>
                        {(theme) =>
                            <Icon icon={CloseIcon} color={theme.texts.secondary} size={1.5}
                                  onClick={props.close} clickable />
                        }
                    </ThemeConsumer>
                </div>
                <div className={styles.body}>
                    { props.children }  // The useContext or the <context.Consumer> doesn't work for the children
                </div>
            </ThemeProvider>
            // ...
        </>
    )
}

Upvotes: 0

Views: 886

Answers (1)

R&#233;mi Bernard
R&#233;mi Bernard

Reputation: 119

I've just misplaced my <context.Consumer> outside of the Popup component and I learned that useContext doesn't work when overriding the Provider.

Upvotes: 1

Related Questions