Last active
August 10, 2023 17:43
-
-
Save nandordudas/2b0904b7d3e01fbfe1c8b6effe172cb6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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