skip to Main Content

I am practicing useContext hook with Next.js if it matters and created a small site that changes background color of <Paragraph></Paragraph> element for printing text and <Button></Button> element to change theme. I organized it with <Layout /> where button and paragraph are placed.

As written in tutorial, I can place this theme change logic into onClick method and it will work. It actually works, but only if I put a simple <button></button> element, but not react component:

<ThemeContext.Provider value={theme}>
                  <Paragraph>Hello Context</Paragraph>
 // Doesn't work ---> <Button type="button" onClick={() => {theme === 'dark' ? setTheme('light') : setTheme('dark')}}>Change theme</Button>
 // Works        ---> <button type="button" onClick={() => {theme === 'dark' ? setTheme('light') : setTheme('dark')}}>Change theme</button>
            </ThemeContext.Provider>

Paragraph.tsx:

import { useContext, useState, ReactNode, DetailedHTMLProps, HTMLAttributes } from 'react';
import styles from './Paragraph.module.scss'
import cn from 'classnames'
import { ThemeContext } from '../../Layout/Layout'

interface ParagraphProps extends DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement> {  
      children: ReactNode
}

export const Paragraph = ({ children, className }: ParagraphProps): JSX.Element => {
      const theme = useContext<string>(ThemeContext)

      return (
            <p className={cn(styles.p, className, {
                  [styles.paraDark]: theme === 'dark',
                  [styles.paraLight]: theme === 'light'
            })}>
                  {children}
            </p>
      )
}

Button.tsx:

import { DetailedHTMLProps, ButtonHTMLAttributes, ReactNode, useContext, useState, MouseEventHandler } from 'react';
import { ThemeContext } from 'Layout/Layout';
import cn from 'classnames'
import styles from './Button.module.scss'

interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
      children: ReactNode
}
 
export const Button = ({ children, className}: ButtonProps ): JSX.Element => {
      const theme = useContext<string>(ThemeContext)

      return (
            <button
                  className={cn(styles.button, className, {
                        [styles.buttonDark]: theme === 'dark',
                        [styles.buttonLight]: theme === 'light'
                  })}
            >
                  {children}
            </button>
      )
}

Layout.tsx:

import { Paragraph, Button } from "Components";
import { createContext, useState } from 'react';

interface Layout {}

export const ThemeContext = createContext<string>('')

export const Layout = (): JSX.Element => {
      const [theme, setTheme] = useState<string>('dark')

      // Putting this function into onClick event also doesn't help
      const handleChangeThemeButtonClick = () => {
            if (theme === 'dark')
                  setTheme('light')
            else if (theme === 'light')
                  setTheme('dark')
      }

      return (
            <ThemeContext.Provider value={theme}>
                  <Paragraph>Hello Context</Paragraph>
                  <Button type="button" onClick={() => {theme === 'dark' ? setTheme('light') : setTheme('dark')}}>Change theme</Button>
                  <button type="button" onClick={() => {theme === 'dark' ? setTheme('light') : setTheme('dark')}}>Change theme</button>
            </ThemeContext.Provider>
      )
}

I was learing this hook on react beta docs and my code looks identical to what’s written in this tutorial.

2

Answers


  1. It seems like you haven’t added an onClick handler to the Button component. You can add an onClick event listener to the button element inside the Button component like this:

    import { DetailedHTMLProps, ButtonHTMLAttributes, ReactNode } from 'react';
    
    interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
      children: ReactNode;
      className?: string;
      onClick?: () => void;
    }
    
    export const Button = ({ children, className, onClick }: ButtonProps): JSX.Element => {
      return (
        <button className={className} onClick={onClick}>
          {children}
        </button>
      );
    };
    

    Dont forget the props which handles onClick, i would suggest to call it something else eg "onPress" not to confuse with native onClick event.

    Login or Signup to reply.
  2. Your Button component doesn’t have an onClick property.

    You need to pass it down as a prop and call it from the native button onClick property.

    Since you’re using typescript, give it the appropriate type of MouseEventHandler<HTMLButtonElement>, and mark it as optional (with ? after the property name in the type declaration) in case you want to use Button without an onClick event.

    import { DetailedHTMLProps, ButtonHTMLAttributes, ReactNode, useContext, useState, MouseEventHandler } from 'react';
    import { ThemeContext } from 'Layout/Layout';
    import cn from 'classnames'
    import styles from './Button.module.scss'
    
    interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
          children: ReactNode
          onClick?: MouseEventHandler<HTMLButtonElement> // <= pass it down as a prop with the appropriate type
    }
     
    export const Button = ({ children, className, onClick }: ButtonProps ): JSX.Element => {
          const theme = useContext<string>(ThemeContext)
    
          return (
                <button
                      className={cn(styles.button, className, {
                            [styles.buttonDark]: theme === 'dark',
                            [styles.buttonLight]: theme === 'light'
                      })}
                      onClick={onClick}
                >
                      {children}
                </button>
          )
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search