By using ReturnType we don't have to manually write type for Context
See also gist for SolidJS https://gist.github.com/JLarky/a46055f673a2cb021db1a34449e3be07
And original tweet https://twitter.com/JLarky/status/1554152932425117697
By using ReturnType we don't have to manually write type for Context
See also gist for SolidJS https://gist.github.com/JLarky/a46055f673a2cb021db1a34449e3be07
And original tweet https://twitter.com/JLarky/status/1554152932425117697
| import React from "react"; | |
| import { useImmer } from "use-immer"; | |
| function useProviderValue() { | |
| const [moved, setMoved] = React.useState(false); | |
| const [point, setPoint] = useImmer<{ | |
| x: number; | |
| y: number; | |
| }>({ x: 0, y: 0 }); // using immer to illustrate that you can easily derive setPoint type instead of writing types for Context manually | |
| const value = React.useMemo( | |
| () => ({ | |
| moved, | |
| setMoved, | |
| point, | |
| setPoint, | |
| }), | |
| [moved, point, setPoint] | |
| ); | |
| return value; | |
| } | |
| export type Context = ReturnType<typeof useProviderValue>; | |
| const Context = React.createContext<Context | undefined>(undefined); | |
| Context.displayName = "Context"; | |
| export const Provider = (props: React.PropsWithChildren) => { | |
| const value = useProviderValue(); | |
| return <Context.Provider value={value} {...props} />; | |
| }; | |
| export function useContext() { | |
| const context = React.useContext(Context); | |
| if (context === undefined) { | |
| throw new Error(`useContext must be used within a Provider`); | |
| } | |
| return context; | |
| } | |
| export function useMoved() { | |
| const { moved } = useContext(); | |
| return moved; | |
| } | |
| export function useListenMouseMove() { | |
| const { setMoved, setPoint } = useContext(); | |
| const isMounted = React.useRef(false); | |
| React.useEffect(() => { | |
| isMounted.current = true; | |
| const listen = (e: MouseEvent) => { | |
| if (isMounted.current) { | |
| setPoint(draft => { | |
| draft.x = e.x; | |
| draft.y = e.y; | |
| }); | |
| setMoved(true); | |
| } | |
| }; | |
| document.addEventListener("mousemove", listen); | |
| return () => { | |
| isMounted.current = false; | |
| document.removeEventListener("mousemove", listen); | |
| }; | |
| }, [setMoved, setPoint]); | |
| return; | |
| } | |
| export const Example = () => { | |
| return ( | |
| <Provider> | |
| <Test1 /> | |
| <Test2 /> | |
| </Provider> | |
| ); | |
| }; | |
| const Test1 = () => { | |
| useListenMouseMove(); | |
| return null; | |
| }; | |
| const Test2 = () => { | |
| const { point } = useContext(); | |
| const hasMoved = useMoved(); | |
| return ( | |
| <> | |
| {hasMoved && ( | |
| <span> | |
| ({point.x},{point.y}) | |
| </span> | |
| )} | |
| </> | |
| ); | |
| }; |
| // minimal example | |
| import type { PropsWithChildren } from "react"; | |
| import { createContext, useContext, useMemo, useState } from "react"; | |
| function useProviderValue() { | |
| const [isDark, setIsDark] = useState(false); | |
| return useMemo(() => ({ isDark, setIsDark }), []); | |
| } | |
| export type Context = ReturnType<typeof useProviderValue>; | |
| const DarkContext = createContext<Context | undefined>(undefined); | |
| DarkContext.displayName = "DarkProvider"; | |
| export const DarkProvider = (props: PropsWithChildren) => { | |
| const value = useProviderValue(); | |
| return <DarkContext.Provider value={value} {...props} />; | |
| }; | |
| export function useDark() { | |
| const context = useContext(DarkContext); | |
| if (context === undefined) { | |
| throw new Error(`useDark must be used within a DarkProvider`); | |
| } | |
| return context; | |
| } | |
| export function useIsDark() { | |
| return useDark().isDark; | |
| } | |
| export function useSetDark() { | |
| return useDark().setIsDark; | |
| } |
| import type { PropsWithChildren } from "react"; | |
| import { createContext, useContext, useMemo, useState } from "react"; | |
| function useProviderValue() { | |
| const [isDark, setIsDark] = useState(false); | |
| const value = useMemo( | |
| () => ({ | |
| isDark, | |
| setIsDark, | |
| }), | |
| [] | |
| ); | |
| return value; | |
| } | |
| export type Context = ReturnType<typeof useProviderValue>; | |
| const DarkContext = createContext<Context>({ | |
| isDark: false, | |
| setIsDark: () => {}, | |
| }); | |
| DarkContext.displayName = "DarkProvider"; | |
| export const DarkProvider = (props: PropsWithChildren) => { | |
| const value = useProviderValue(); | |
| return <DarkContext.Provider value={value} {...props} />; | |
| }; | |
| export function useDark() { | |
| return useContext(DarkContext); | |
| } | |
| export function useIsDark() { | |
| return useDark().isDark; | |
| } | |
| export function useSetDark() { | |
| return useDark().setIsDark; | |
| } |
Thank you dude. I've spent two days trying to figure out how to type context efficiently.