Skip to content

Instantly share code, notes, and snippets.

@GGrassiant
Last active March 28, 2023 12:49
Show Gist options
  • Save GGrassiant/36f01e8c1b1c6f069fee9cc32706892d to your computer and use it in GitHub Desktop.
Save GGrassiant/36f01e8c1b1c6f069fee9cc32706892d to your computer and use it in GitHub Desktop.
React Context TS
(David Gray)
import { createContext, useReducer, ChangeEvent, ReactElement, useCallback, useContext } from "react"
type StateType = {
count: number;
text: string;
}
export const initState: StateType = { count: 0, text: '' }
const enum REDUCER_ACTION_TYPE {
INCREMENT,
DECREMENT,
NEW_INPUT,
}
type ReducerAction = {
type: REDUCER_ACTION_TYPE,
payload?: string,
}
const reducer = (state: StateType, action: ReducerAction): StateType => {
switch (action.type) {
case REDUCER_ACTION_TYPE.INCREMENT:
return { ...state, count: state.count + 1 }
case REDUCER_ACTION_TYPE.DECREMENT:
return { ...state, count: state.count - 1 }
case REDUCER_ACTION_TYPE.NEW_INPUT:
return { ...state, text: action.payload ?? '' }
default:
throw new Error()
}
}
const useCounterContext = (initState: StateType) => {
const [state, dispatch] = useReducer(reducer, initState)
const increment = useCallback(() => dispatch({ type: REDUCER_ACTION_TYPE.INCREMENT }), [])
const decrement = useCallback(() => dispatch({ type: REDUCER_ACTION_TYPE.DECREMENT }), [])
const handleTextInput = useCallback((e: ChangeEvent<HTMLInputElement>) => {
dispatch({
type: REDUCER_ACTION_TYPE.NEW_INPUT,
payload: e.target.value
})
}, [])
return { state, increment, decrement, handleTextInput }
}
type UseCounterContextType = ReturnType<typeof useCounterContext>
const initContextState: UseCounterContextType = {
state: initState,
increment: () => { },
decrement: () => { },
handleTextInput: (e: ChangeEvent<HTMLInputElement>) => { },
}
export const CounterContext = createContext<UseCounterContextType>(initContextState)
type ChildrenType = {
children?: ReactElement | ReactElement[] | undefined
}
export const CounterProvider = ({
children, ...initState
}: ChildrenType & StateType): ReactElement => {
return (
<CounterContext.Provider value={useCounterContext(initState)}>
{children}
</CounterContext.Provider>
)
}
type UseCounterHookType = {
count: number,
increment: () => void,
decrement: () => void,
}
export const useCounter = (): UseCounterHookType => {
const { state: { count }, increment, decrement } = useContext(CounterContext)
return { count, increment, decrement }
}
type UseCounterTextHookType = {
text: string,
handleTextInput: (e: ChangeEvent<HTMLInputElement>) => void,
}
export const useCounterText = (): UseCounterTextHookType => {
const { state: { text }, handleTextInput } = useContext(CounterContext)
return { text, handleTextInput }
}
// To be used like <CounterProvider count={initState.count} text={initState.text}>
// Another way to handle Context in Typescript
export const useSomething = () => {
const context = useContext(SomethingContext);
if (!context) {
throw new Error('useSomething must be used within a SomethingProvider');
}
return context;
};
interface SomethingProviderProps {
children: React.ReactNode;
loader?: JSX.Element;
}
interface SomethingContextData {
something: Something;
}
export const SomethingContext = createContext<SomethingContextData | undefined>(undefined);
export function SomethingProvider({ children, loader = <AppLayoutLoader /> }: SomethingProviderProps) {
const { data, isLoading, isError } = useSomethingQuery();
const value = useMemo(() => {
if (data) {
return { data };
}
return undefined;
}, [data]);
if (isLoading) return loader;
if (isError || !value) return <ErrorMessage />;
return <SomethingContext.Provider value={value}>{children}</SomethingContext.Provider>;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment