-
-
Notifications
You must be signed in to change notification settings - Fork 68
Description
Steps to reproduce
Consider the following state:
interface AppState {
someString: string;
someBoolean: boolean;
partialObject: Partial<{ x: number; y: number }>;
anotherBoolean: boolean;
anotherString: string;
}
export interface AppEvents {
setValue: string;
}If you plug this into the useStoreon hook to use in your component, it might look something like this:
import { FunctionComponent, PropsWithChildren } from 'react';
import { useStoreon } from 'storeon/react';
import { AppEvents, AppState } from './app.state';
const MyComponent: FunctionComponent<PropsWithChildren> = ({ children }) => {
const {
someString,
someBoolean,
anotherBoolean,
anotherString
} = useStoreon<AppState, AppEvents>('someString','someBoolean','anotherBoolean');
const attributeValue = someString.length > 0 && anotherString.length > 0 && someBoolean && !anotherBoolean ? 'value1' : 'value2';
return <div data-tranformed-attribute={attributeValue}>{children}</div>
};This code compiles perfectly fine and reports absolutely no type issues. However, as soon as you actually try to use the component with a perfectly valid state, you get the following error:
Uncaught TypeError: anotherString is undefined
Those deeply familiar with the library might have caught the fact that 'anotherString' was missing from the useStoreon hook call parameters, which is causing the value that would otherwise always be a string to return undefined, breaking the code during runtime.
Fix ideas
Some libraries make use of function overloads to achieve the desired outcome, this does require however that the generic type parameters be handled differently:
// Without parameters, no property can possibly be returned
export function useStoreon<State, Events>(): Record<string, undefined>;
// Adding overloads for individual parameters
export function useStoreon<State, Events, Key1 extends keyof State>(key1: Key1): Pick<State, Key1>;
export function useStoreon<State, Events, Key1 extends keyof State, Key2 extends keyof State>(key1: Key1, key2: Key2): Pick<State, Key1 | Key2>;
// etc.This does have the drawback however that you would manually have to duplicate each key in both the type arguments as well as the function properties. A potential alternative would be to change the function signature to return a new function which is fully type safe without having to specify more than the state and event types:
export function createUseStoreon<State, Events>():
| (<Key1 extends keyof State>(key1: Key1) => Pick<State, Key1>)
| (<Key1 extends keyof State, Key2 extends keyof State>(key1: Key1, key2: Key2) => Pick<State, Key1 | Key2>);I'm not really sure if there is a better way, but this is quite a frustrating issue that has caused me a lot of unnecessary debugging time and I would very much appreciate if this could be addressed somehow.