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.
>Solution :
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>
)
}