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
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:
Dont forget the props which handles onClick, i would suggest to call it something else eg "onPress" not to confuse with native onClick event.
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.