Skip to content

Instantly share code, notes, and snippets.

@nandordudas
Last active August 10, 2023 17:43
Show Gist options
  • Save nandordudas/2b0904b7d3e01fbfe1c8b6effe172cb6 to your computer and use it in GitHub Desktop.
Save nandordudas/2b0904b7d3e01fbfe1c8b6effe172cb6 to your computer and use it in GitHub Desktop.
import type { Dispatch, PropsWithChildren, SetStateAction } from 'react'
import { createContext, useContext, useState } from 'react'
type Prettify<T> = {
[K in keyof T]: T[K]
} & NonNullable<unknown>
type Nullable<T> = T | null
type Theme = 'dark' | 'light'
interface ThemeContextProps<T> {
setTheme: Dispatch<SetStateAction<T>>
theme: T
}
type NullableThemeContext = Prettify<Nullable<ThemeContextProps<Theme>>>
const initialValue: NullableThemeContext = null
const ThemeContext = createContext<NullableThemeContext>(initialValue)
function ThemeContextProvider({ children }: PropsWithChildren) {
const [theme, setTheme] = useState<Theme>('light')
return (
<ThemeContext.Provider value={{ setTheme, theme }}>
{children}
</ThemeContext.Provider>
)
}
// We missed this custom hook.
function useTheme() {
const context = useContext(ThemeContext)
assert(isNotNull(context), '[useTheme]: must be used within a ThemeContextProvider')
return context
}
export function App() {
const { theme } = useTheme()
return (
<ThemeContextProvider>
{/* Value will always be dark or light. */}
{theme}
</ThemeContextProvider>
)
}
function assert(condition: boolean, message: string): asserts condition {
if (!condition)
raise(message)
}
function raise(message: string): never {
throw new Error(message)
}
function isNotNull<T>(value: any): value is NonNullable<T> {
return value !== null
}
import type { Dispatch, PropsWithChildren } from 'react'
import { createContext, useContext, useReducer } from 'react'
type Prettify<T> = {
[K in keyof T]: T[K]
} & NonNullable<unknown>
type Nullable<T> = T | null | undefined
// Optional properties are not my favourites.
interface Theme {
mode?: 'dark' | 'light'
primaryColor?: string
secondaryColor?: string
}
type ThemeAction = {
payload: Nullable<Partial<Theme>>
type: 'update_primary_color'
}
interface ThemeContextProps<T> {
dispatch: Dispatch<ThemeAction>
theme: T
}
type NullableThemeContext = Prettify<Nullable<ThemeContextProps<Theme>>>
const initialValue: NullableThemeContext = null
const ThemeContext = createContext<NullableThemeContext>(initialValue)
function themeReducer(state: Nullable<Theme>, { payload, type }: ThemeAction) {
const actionMap: Record<ThemeAction['type'], Theme> = {
update_primary_color: {
...state,
...payload,
},
}
return actionMap[type] ?? state
}
function ThemeContextProvider({ children }: PropsWithChildren) {
const [theme, dispatch] = useReducer(themeReducer, {})
return (
<ThemeContext.Provider value={{ dispatch, theme }}>
{children}
</ThemeContext.Provider>
)
}
function useTheme() {
const context = useContext(ThemeContext)
assert(isNotNull(context), '[useTheme]: must be used within a ThemeContextProvider')
return context
}
export function App() {
const { dispatch, theme } = useTheme()
function updatePrimaryColor() {
dispatch({
type: 'update_primary_color',
payload: {
primaryColor: 'olive',
},
})
}
return (
<ThemeContextProvider>
{theme}
<button onClick={updatePrimaryColor}>
Update primary color
</button>
</ThemeContextProvider>
)
}
function assert(condition: boolean, message: string): asserts condition {
if (!condition)
raise(message)
}
function raise(message: string): never {
throw new Error(message)
}
function isNotNull<T>(value: any): value is NonNullable<T> {
return value !== null
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment