I have only been using TypeScript a couple months, and I just noticed that the compiler is not enforcing the shape of data a function accepts if that function is accessed through React.useContext().
This setup here is not exactly what I have going on, but it more or less shows the problem I am trying to figure out.
import * as React from 'react';
//==>> Reducer State Interface
interface InitialStateInterface {
handleSettingFooBar: Function;
foo: string | null;
bar: boolean;
}
//==>> Function Interface
interface PayloadInterface {
foo?: string | null;
bar?: boolean;
}
//==> Reducer
interface ReducerInterface {
type: string;
payload: PayloadInterface;
}
enum ActionTypes {
SET_FOO = 'SET_FOO',
SET_BAR = 'SET_BAR',
}
const initialState: InitialStateInterface = {
handleSettingFooBar: () => null,
foo: null,
bar: false,
};
const SomeContext = React.createContext<InitialStateInterface>(initialState);
const ContextReducer = (state: any, { type, payload }: ReducerInterface) => {
switch (type) {
case ActionTypes.SET_FOO:
return { ...state, foo: payload.foo };
case ActionTypes.SET_BAR:
return { ...state, bar: payload.bar };
default:
return state;
}
};
const SomeProvider = () => {
const [state, dispatch] = React.useReducer(ContextReducer, initialState);
function handleSettingFooBar(data: PayloadInterface) {
let { foo, bar } = data;
if (foo) dispatch({ type: ActionTypes.SET_FOO, payload: { foo } });
if (bar) dispatch({ type: ActionTypes.SET_BAR, payload: { bar } });
}
/** Okay, of course, no error */
handleSettingFooBar({ foo: 'test' });
/** Error as expected, type does not match */
handleSettingFooBar({ foo: false });
/** Error as expected, key does not exist */
handleSettingFooBar({ randomKey: 'cant do this' });
return <SomeContext.Provider value={{ ...state, handleSettingFooBar }} />;
};
/* ===> But when writing a component that uses that context <=== */
export const SomeComponent = () => {
const { handleSettingFooBar } = React.useContext(SomeContext);
/** Why is the compiler not yelling at me here??? */
handleSettingFooBar({ randomKey: 'hahaha' });
};
export { SomeProvider, SomeContext };
I have tried putting the interface in when calling the context, like this:
const { handleSettingFooBar } = React.useContext<InitialStateInterface>(SomeContext);
But that made no difference.
I am expecting that if somebody is authoring a component that uses this context and its provided functions, that it will regulate the data (at compile time, of course) they try to pass in so a generic setter may not add a value that does not belong in the context reducer state.
Please help, thanks!
CodePudding user response:
The SomeContext
has the InitialStateInterface
type which defines handleSettingFooBar
as handleSettingFooBar: Function
, and it does not know how you actually implemented it.
You can change that to handleSettingFooBar: (data:PayloadInterface) => void
and then the typescript would know what kind of input should be allowed for it.