diff --git a/public-types/reflect.d.ts b/public-types/reflect.d.ts index 10d1e38..1a403ef 100644 --- a/public-types/reflect.d.ts +++ b/public-types/reflect.d.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import type { EventCallable, Store } from 'effector'; +import type { EventCallable, Show, Store } from 'effector'; import type { useUnit } from 'effector-react'; import type { ComponentType, FC, PropsWithChildren, ReactHTML } from 'react'; @@ -12,6 +12,12 @@ type Hooks = { unmounted?: EventCallable | (() => unknown); }; +/** + * `bind` object type: + * prop key -> store (unwrapped to reactive subscription) or any other value (used as is) + * + * Also handles some edge-cases like enforcing type inference for inlined callbacks + */ type BindFromProps = { [K in keyof Props]?: K extends UnbindableProps ? never @@ -25,6 +31,18 @@ type BindFromProps = { : Store | Props[K]; }; +/** + * Computes final props type based on Props of the view component and Bind object. + * + * Props that are "taken" by Bind object are made **optional** in the final type, + * so it is possible to owerrite them in the component usage anyway + */ +type FinalProps> = Show< + Omit & { + [K in Extract]?: Props[K]; + } +>; + // relfect types /** * Operator that creates a component, which props are reactively bound to a store or statically - to any other value. @@ -49,7 +67,7 @@ export function reflect>(config: { * This configuration is passed directly to `useUnit`'s hook second argument. */ useUnitConfig?: UseUnitConfig; -}): FC>; +}): FC>; // Note: FC is used as a return type, because tests on a real Next.js project showed, // that if theoretically better option like (props: ...) => React.ReactNode is used, @@ -83,7 +101,7 @@ export function createReflect>( */ useUnitConfig?: UseUnitConfig; }, -) => FC>; +) => FC>; // list types type PropsifyBind = { @@ -199,7 +217,7 @@ export function variant< */ useUnitConfig?: UseUnitConfig; }, -): FC>; +): FC>; // fromTag types type GetProps = Exclude< diff --git a/type-tests/types-reflect.tsx b/type-tests/types-reflect.tsx index 232df8b..cb5b039 100644 --- a/type-tests/types-reflect.tsx +++ b/type-tests/types-reflect.tsx @@ -73,6 +73,35 @@ import { expectType } from 'tsd'; expectType(ReflectedInput); } +// reflect should not allow wrong props in final types +{ + const Input: React.FC<{ + value: string; + onChange: (newValue: string) => void; + color: 'red'; + }> = () => null; + const $value = createStore(''); + const changed = createEvent(); + + const ReflectedInput = reflect({ + view: Input, + bind: { + value: $value, + onChange: changed, + }, + }); + + const App: React.FC = () => { + return ( + + ); + }; + expectType(App); +} + // reflect should allow not-to pass required props - as they can be added later in react { const Input: React.FC<{ @@ -104,6 +133,65 @@ import { expectType } from 'tsd'; expectType(AppFixed); } +// reflect should make "binded" props optional - so it is allowed to overwrite them in react anyway +{ + const Input: React.FC<{ + value: string; + onChange: (newValue: string) => void; + color: 'red'; + }> = () => null; + const $value = createStore(''); + const changed = createEvent(); + + const ReflectedInput = reflect({ + view: Input, + bind: { + value: $value, + onChange: changed, + }, + }); + + const App: React.FC = () => { + return ; + }; + + const AppFixed: React.FC = () => { + return ; + }; + expectType(App); + expectType(AppFixed); +} + +// reflect should not allow to override "binded" props with wrong types +{ + const Input: React.FC<{ + value: string; + onChange: (newValue: string) => void; + color: 'red'; + }> = () => null; + const $value = createStore(''); + const changed = createEvent(); + + const ReflectedInput = reflect({ + view: Input, + bind: { + value: $value, + onChange: changed, + color: 'red', + }, + }); + + const App: React.FC = () => { + return ( + + ); + }; + expectType(App); +} + // reflect should allow to pass EventCallable as click event handler { const Button: React.FC<{ diff --git a/type-tests/types-variant.tsx b/type-tests/types-variant.tsx index a1d4ca4..bd0fd13 100644 --- a/type-tests/types-variant.tsx +++ b/type-tests/types-variant.tsx @@ -243,7 +243,6 @@ import { expectType } from 'tsd'; Another slot type(} /> Should report error for "name"} - // @ts-expect-error name="kek" />