Skip to content

Instantly share code, notes, and snippets.

@sumanbh
Forked from swyxio/createCtx-noNullCheck.tsx
Created July 4, 2019 20:17
Show Gist options
  • Save sumanbh/749fa78b635109158e16c21abda7b66f to your computer and use it in GitHub Desktop.
Save sumanbh/749fa78b635109158e16c21abda7b66f to your computer and use it in GitHub Desktop.
better createContext APIs with setters, and no default values, in Typescript
// create context with no upfront defaultValue
// without having to do undefined check all the time
function createCtx<A>() {
const ctx = React.createContext<A | undefined>(undefined)
function useCtx() {
const c = React.useContext(ctx)
if (!c) throw new Error("useCtx must be inside a Provider with a value")
return c
}
return [useCtx, ctx.Provider] as const
}
// usage - no need to specify value upfront!
export const [useCtx, SettingProvider] = createCtx<string>()
export function App() {
// get a value from a hook, must be in a component
const key = useLocalStorage('key')
return (
<SettingProvider value={key}>
<Component />
</SettingProvider>
)
}
export function Component() {
const key = useCtx() // can still use without null check!
return <div>{key}</div>
}
function useLocalStorage(a: string) {
return 'secretKey' + a
}
type ReducerType<State> = (state: State, action: State) => State
export function createCtx<A>(defaultValue: A, reducer: ReducerType<A>) {
type DispatchType = React.Dispatch<typeof defaultValue>
const defaultDispatch: DispatchType = () => defaultValue
const ctx = React.createContext({ state: defaultValue, dispatch: defaultDispatch })
function Provider(props: React.PropsWithChildren<{}>) {
const [state, dispatch] = React.useReducer(reducer, defaultValue)
return <ctx.Provider value={{ state, dispatch }} {...props} />
}
return [ctx, Provider] as const
}
// usage
enum States {
Green,
Red,
Yellow
}
function reducer(state: States) {
if (state === States.Green) return States.Yellow
if (state === States.Yellow) return States.Red
return States.Green
}
const [ctx, TextProvider] = createCtx(States.Green, reducer)
export const TextContext = ctx
export function App() {
return (
<TextProvider>
<Component />
</TextProvider>
)
}
export function Component() {
const { state, dispatch } = React.useContext(ctx)
return (
<div>
{state}
<button onClick={() => dispatch(state)}>Toggle</button>
</div>
)
}
export function createCtx<A>(defaultValue: A) {
type UpdateType = React.Dispatch<React.SetStateAction<typeof defaultValue>>
const defaultUpdate: UpdateType = () => defaultValue
const ctx = React.createContext({ state: defaultValue, update: defaultUpdate })
function Provider(props: React.PropsWithChildren<{}>) {
const [state, update] = React.useState(defaultValue)
return <ctx.Provider value={{ state, update }} {...props} />
}
return [ctx, Provider] as const
}
// usage
const [ctx, TextProvider] = createCtx("someText")
export const TextContext = ctx
export function App() {
return (
<TextProvider>
<Component />
</TextProvider>
)
}
export function Component() {
const { state, update } = React.useContext(ctx)
return (
<label>
{state}
<input type="text" onChange={e => update(e.target.value)} />
</label>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment