Skip to content

Instantly share code, notes, and snippets.

@NicholasBoll
Created May 7, 2025 20:35
Show Gist options
  • Save NicholasBoll/7bf2fe624e80ba04109b76b5c0160af2 to your computer and use it in GitHub Desktop.
Save NicholasBoll/7bf2fe624e80ba04109b76b5c0160af2 to your computer and use it in GitHub Desktop.
import React from 'react';
import { assert } from './assert';
import { MergeProps } from './mergeProps';
function defaultGetElemProps(input) {
return input;
}
export const createContainer = (as) => ({ displayName, modelHook, elemPropsHook, defaultContext, subComponents, }) => {
assert(modelHook.Context, 'createContainer only works on models with context. Please use `createModelHook` to create the `modelHook`');
const Context = modelHook.Context;
return _jsxs(Props, { children: ["( Component: ( props: CompoundProps", _jsx(Props, {}), ", TElemPropsHook, E>, Element: E extends undefined ? never : E, model: TModelHook extends (config: infer TConfig) => infer TModel ? TModel : never ) => JSX.Element | null ): (TModelHook extends (config: infer TConfig) => infer TModel ? E extends undefined ? ComponentM", _jsx(Props, {}), " & TConfig, TModel> : ElementComponentM", _jsx(
// E is not `undefined` here, but Typescript thinks it could be, so we add another `undefined`
// check and cast to a `React.FC` to match a valid signature for `ElementComponent`.
// `React.FC` was chosen as the simplest valid interface.
E, { extends: true, undefined: true }), " ? React.FC : E, Props & TConfig, TModel > : never) & SubComponents => ", , "const ReturnedComponent = React.forwardRef", _jsx(E, {}), ", Props & ", as ? : React.ElementType, " & ", model ? : any, ">((", as, ": asOverride, model, ...props}, ref) => ", , "const localModel = useDefaultModel(model, props, modelHook, asOverride); const elemProps = ((modelHook as any).getElemProps || defaultGetElemProps)(props); const finalElemProps = elemPropsHook ? (elemPropsHook as any)(localModel, elemProps, ref) : elemProps; // make sure there's always a ref being passed, even if there are no elemProps hooks to run if (ref && !finalElemProps.hasOwnProperty('ref')) ", finalElemProps.ref = ref, "; } return React.createElement( Context.Provider,", value, ": localModel}, Component( finalElemProps, // Cast to `any` to avoid: \"ts(2345): Type 'undefined' is not assignable to type 'E extends // undefined ? never : E'\" I'm not sure I can actually cast to this conditional type and it // doesn't actually matter, so cast to `any` it is. (asOverride || as) as any, localModel ) ); }); Object.keys(subComponents || ", ").forEach(key => ",
// `ReturnedComponent` is a `React.ForwardRefExoticComponent` which has no additional keys so
// we'll cast to `Record<string, any>` for assignment. Note the lack of type checking
// properties. Take care when changing the runtime of this function.
ReturnedComponent[key] = subComponents[key], "; // Add a displayName if one isn't already created. This prevents us from having to add // `displayName` to subcomponents if a container component has a `displayName` if (displayName && !(subComponents as Record", _jsx("string", {}), ", any>)[key].displayName) ", subComponents[key].displayName = `${displayName}.${key}`, "; } }); ReturnedComponent.displayName = displayName; (ReturnedComponent as any).__hasModel = true; // The `any`s are here because `ElementComponent` takes care of the `as` type and the // `ReturnComponent` type is overridden (ReturnedComponent as any).as = memoize( (as: any) => createContainer(as)(", (displayName, subComponents, modelHook, elemPropsHook), ")( Component as any ), as => as ); // Cast as `any`. We have already specified the return type. Be careful making changes to this // file due to this `any` `ReturnedComponent` is a `React.ForwardRefExoticComponent`, but we want // it to be either an `Component` or `ElementComponent` return ReturnedComponent as any; }; }; /** * If elemProps returns `null` for a prop, that prop is to be removed from the prop * list. This is useful for passing props to an elemProp hook that should not be exposed * to the DOM element */ type RemoveNull", _jsxs(T, { children: [" = ", [K in keyof, T], ": Exclude", _jsx(T, {}), "[K], null>}; /** * Props for the compound component based on props, elemPropsHook, and element. It will * return a prop interface according to all these inputs. The following will be added to * the passed in `Props` type: * - The prop interface returned by the `elemPropsHook` function * - if there is no detected `ref` in the `Props` interface, a `ref` will be added based on the element type E * - if there is no detected `children` in the `Props` interface, `children` will be added based on `ReactNode` */ type CompoundProps", _jsx(Props, {}), ", TElemPropsHook, E> = Props & (TElemPropsHook extends (...args: any[]) => infer TProps // try to infer TProps returned from the elemPropsHook function ? RemoveNull", _jsx(Omit, {}), " & ", ref, ": ExtractRef", _jsxs(E, { children: ["}> : ", ref, ": ExtractRef", _jsxs(E, { children: ["}) & (Props extends ", children, ": any} ? ", ": ", children ? : React.ReactNode, "; }); export const createSubcomponent =", _jsx(E, { extends: true }), "| keyof JSX.IntrinsicElements | React.ComponentType | ElementComponent", _jsx("any", {}), ", any> | ElementComponentM", _jsx("any", {}), ", any, any> | undefined = undefined, >( as?: E ) =>", _jsx(TElemPropsHook, {}), ", // normally we'd put a constraint here, but doing so causes the `infer` below to fail to infer the return props TModelHook extends ((config: any) => Model", _jsx("any", {}), ", any>) & ", Context ? : React.Context, ", SubComponents = ", ", >(", (displayName,
modelHook,
elemPropsHook,
subComponents,
), ": ",
/** @deprecated ⚠️ `displayName` has been deprecated and will be removed in a future major version. You no longer need to use `displayName`. A `displayName` will be automatically added if it belongs to a container. */
displayName ? : string, "; modelHook: TModelHook; elemPropsHook?: TElemPropsHook; subComponents?: SubComponents; }) => ", assert(modelHook.Context, 'createSubcomponent only works on models with context. Please use `createModelHook` to create the `modelHook`'), "; return ", _jsx(Props, {}), " = ", ">( Component: ( props: CompoundProps", _jsx(Props, {}), ", TElemPropsHook, E>, Element: E extends undefined ? never : E, model: TModelHook extends (...args: any[]) => infer TModel ? TModel : never ) => JSX.Element | null ): (TModelHook extends (...args: any[]) => infer TModel ? ElementComponentM", _jsx(
// E is not `undefined` here, but Typescript thinks it could be, so we add another `undefined`
// check and cast to a `React.FC` to match a valid signature for `ElementComponent`.
// `React.FC` was chosen as the simplest valid interface.
E, { extends: true, undefined: true }), " ? React.FC : E, Props, TModel > : never) & SubComponents => ", , "const ReturnedComponent = React.forwardRef", _jsx(E, {}), ", Props & ", as ? : React.ElementType, " & ", model ? : any, "; elemPropsHook?: (...args: any) => any} >((", as, ": asOverride, model, elemPropsHook: additionalPropsHook, ...props}, ref) => ", , "const localModel = useModelContext(modelHook.Context!, model, asOverride); // maybeModelProps reattached the `model` prop if the passed model is incompatible with the // modelHook's context. This fixes issues when using the `as` prop on model element components // that both have a model const maybeModelProps = model && localModel !== model ? ", ...(props, model, ref), " : ", ...(props, ref), "; const elemProps = elemPropsHook ? (elemPropsHook as any)(localModel, maybeModelProps, ref) : maybeModelProps; return Component( additionalPropsHook ? additionalPropsHook(localModel, elemProps, ref) : elemProps, // Cast to `any` to avoid: \"ts(2345): Type 'undefined' is not assignable to type 'E extends // undefined ? never : E'\" I'm not sure I can actually cast to this conditional type and it // doesn't actually matter, so cast to `any` it is. (asOverride || as) as any, localModel ); }); Object.keys(subComponents || ", ").forEach(key => ",
// `ReturnedComponent` is a `React.ForwardRefExoticComponent` which has no additional keys so
// we'll cast to `Record<string, any>` for assignment. Note the lack of type checking
// properties. Take care when changing the runtime of this function.
ReturnedComponent[key] = subComponents[key], "; }); if (displayName) ", ReturnedComponent.displayName = displayName, "; } (ReturnedComponent as any).__hasModel = true; // The `any`s are here because `ElementComponent` takes care of the `as` type and the // `ReturnComponent` type is overridden (ReturnedComponent as any).as = memoize( (as: any) => createSubcomponent(as)(", (displayName, subComponents, modelHook, elemPropsHook), ")( Component as any ), as => as ); // Cast as `any`. We have already specified the return type. Be careful making changes to this // file due to this `any` `ReturnedComponent` is a `React.ForwardRefExoticComponent`, but we want // it to be either an `Component` or `ElementComponent` return ReturnedComponent as any; }; }; /** * Factory function that creates components to be exported. It enforces React ref forwarding, `as` * prop, display name, and sub-components, and handles proper typing without much boiler plate. The * return type is `Component", _jsx("element", {}), ", Props>` which looks like `Component", _jsx(, {}), "'div', Props>` which is a * clean interface that tells you the default element that is used. */ export const createComponent =", _jsx(E, { extends: true }), "| keyof JSX.IntrinsicElements | React.ComponentType | ElementComponent", _jsx("any", {}), ", any> | undefined = undefined, >( as?: E ) =>", _jsx(P, {}), ", SubComponents = ", ">(", (displayName,
Component,
subComponents,
), ": ",
/** This is what the component will look like in the React dev tools. Encouraged to more easily
* understand the component tree */
displayName ? : string, "; /** The component function. The function looks like: * @example * Component: (", children, ", ref, Element) ",
*
// `Element` is what's passed to the `as` of your component. If no `as` was defined, it
*
// will be the default element. It will be 'div' or even a another Component!
* , " return ( * ", _jsx(Element, { ref: ref, children: children }), "* ) * } * * @example * Component: (", children, ", ref, Element) ",
*
// `Element` can be passed via `as` to the next component
* , " return ( * ", _jsx(AnotherElement, { as: Element, ref: ref, children: children }), "* ) * } */ Component: RefForwardingComponent", _jsx(E, {}), ", P>; /** * Used in container components */ subComponents?: SubComponents; }): (E extends undefined ? Component", _jsxs(P, { children: [": ElementComponent", _jsx(
// E is not `undefined` here, but Typescript thinks it could be, so we add another `undefined`
// check and cast to a `React.FC` to match a valid signature for `ElementComponent`.
// `React.FC` was chosen as the simplest valid interface.
E, { extends: true, undefined: true }), " ? React.FC : E, P >) & SubComponents => ", , "const ReturnedComponent = React.forwardRef", _jsx(E, {}), ", P & ", as ? : React.ElementType, ">( (", as, ": asOverride, ...props}, ref) => ", , "return Component( props as any, ref as ExtractRef", _jsxs(E, { children: [", // Cast to `any` to avoid: \"ts(2345): Type 'undefined' is not assignable to type 'E extends // undefined ? never : E'\" I'm not sure I can actually cast to this conditional type and it // doesn't actually matter, so cast to `any` it is. (asOverride || as) as any ); } ); Object.keys(subComponents || ", ").forEach(key => ",
// `ReturnedComponent` is a `React.ForwardRefExoticComponent` which has no additional keys so
// we'll cast to `Record<string, any>` for assignment. Note the lack of type checking
// properties. Take care when changing the runtime of this function.
ReturnedComponent[key] = subComponents[key], "; }); ReturnedComponent.displayName = displayName; // The `any`s are here because `ElementComponent` takes care of the `as` type and the // `ReturnComponent` type is overridden (ReturnedComponent as any).as = memoize( (as: any) => createComponent(as)(", (displayName, Component, subComponents), "), as => as ); // Cast as `any`. We have already specified the return type. Be careful making changes to this // file due to this `any` `ReturnedComponent` is a `React.ForwardRefExoticComponent`, but we want // it to be either an `Component` or `ElementComponent` return ReturnedComponent as any; }; /** * An `elemPropsHook` is a React hook that takes a model, ref, and elemProps and returns props and * attributes to be spread to an element or component. * * ```tsx * const useMyHook = createElemPropsHook(useMyModel)((model) => ",
* , " return ",
* id, ": model.state.id * } * }) * ``` * * **Note:** If your hook needs to use a ref, it must be forked using `useLocalRef` or `useForkRef` * and return the forked ref: * * ```tsx * const useMyHook = createElemPropsHook(useMyModel)((model, ref, elemProps) => ",
* , " const ", (localRef, elementRef), " = useLocalRef(ref); * * React.useLayoutEffect(() => ",
* console.log('element', localRef.current) // logs the DOM element
* , ", []) * * return ",
* ref, ": elementRef * } * }) * ``` */ export const createElemPropsHook =", _jsx(TModelHook, { extends: true }), " (config: any) => Model", _jsx("any", {}), ", any>>(modelHook: TModelHook) =>", _jsx("const", { PO: true, extends: true, ... }), ", const PI extends ", ">( fn: ( model: TModelHook extends (config: any) => infer TModel ? TModel : Model", _jsx("any", {}), ", any>, ref?: React.Ref", _jsxs("unknown", { children: [", elemProps?: PI ) => PO ): BehaviorHook", _jsx(TModelHook, { extends: true }), " (config: any) => infer TModel ? TModel : Model", _jsx("any", {}), ", any>, PO > => ", , "return ((model, elemProps, ref) => ", , "const props = mergeProps(fn(model, ref, elemProps || (", " as any)), elemProps || (", " as any)); if (!props.hasOwnProperty('ref') && ref) ",
// This is the weird "incoming ref isn't in props, but outgoing ref is in props" thing
// @ts-ignore TS says `ref` isn't on `PO`, but we always add it anyways
props.ref = ref, "; } return props; }) as BehaviorHook", _jsx(TModelHook, { extends: true }), " (config: any) => infer TModel ? TModel : Model", _jsx("any", {}), ", any>, PO >; }; /** * Factory function to crate a behavior hook with correct generic types. It takes a function that * return props and returns a function that will also require `elemProps` and will call `mergeProps` for * you. If your hook makes use of the `ref`, you will have to also use `useLocalRef` to properly fork * the ref. * * @example * const useMyHook = createHook((model: MyModel, ref) => ",
* , " const ", (localRef, elementRef), " = useLocalRef(ref); * // do whatever with `localRef` which is a RefObject * * return ",
* onClick, ": model.events.doSomething, * ref: elementRef, * }; * }); * * // Equivalent to: * const useMyHook = ", _jsxs(P, { extends: true, ..., children: ["( * model: MyModel, * elemProps: P, * ref: React.Ref", _jsxs("unknown", { children: ["* ) => ",
* , " const ", (localRef, elementRef), " = useLocalRef(ref); * // do whatever with `localRef` which is a RefObject * * return mergeProps(",
* onClick, ": model.events.doSomething, * ref: elementRef, * }, elemProps); * }; * * @param fn Function that takes a model and optional ref and returns props */ export const createHook = ", _jsx(M, { extends: true, Model: true }), _jsx("any", {}), ", any>, PO extends ", ", PI extends ", ">( fn: (model: M, ref?: React.Ref", _jsxs("unknown", { children: [", elemProps?: PI) => PO ): BehaviorHook", _jsx(M, {}), ", PO> => ", , "return ((model, elemProps, ref) => ", , "const props = mergeProps(fn(model, ref, elemProps || (", " as any)), elemProps || (", " as any)); if (!props.hasOwnProperty('ref') && ref) ",
// This is the weird "incoming ref isn't in props, but outgoing ref is in props" thing
// @ts-ignore TS says `ref` isn't on `PO`, but we always add it anyways
props.ref = ref, "; } return props; }) as BehaviorHook", _jsx(M, {}), ", PO>; }; /** * @deprecated \u26A0\uFE0F `subModelHook` has been deprecated and will be removed in a future major version. Please use `createSubModelElemPropsHook` instead. */ export const subModelHook = ", _jsx(M, { extends: true, Model: true }), _jsx("any", {}), ", any>, SM extends Model", _jsx("any", {}), ", any>, O extends ", ">( fn: (model: M) => SM, hook: BehaviorHook", _jsx(SM, {}), ", O> ): BehaviorHook", _jsx(M, {}), ", O> => ", , "return ((model: M, props: any, ref: React.Ref", _jsxs("unknown", { children: [") => ", , "return hook(fn(model), props, ref); }) as BehaviorHook", _jsx(M, {}), ", O>; }; /** * Creates an elemPropsHook that returns the elemProps from another hook that is meant for a * subModel. Usually only used when composing elemProps hooks. * * For example: * * ```tsx * const useMySubModel = () => ", "* * const useMyModel = () => ",
* , " const subModel = useMySubModel() * * return ", (
* state,
* events,
* subModel,
* ), "* } * * const useMyComponent = composeHook( * createElemPropsHook(useMyModel)(model => (", id, ": '' })), * createSubModelElemPropsHook(useMyModel)(m => m.subModel, useSomeOtherComponent) * ) * ``` */ export function createSubModelElemPropsHook", _jsx(M, { extends: true }), " () => Model", _jsx("any", {}), ", any>>(modelHook: M) ", , "return ", _jsx(SM, { extends: true, Model: true }), _jsx("any", {}), ", any>, O extends ", ">( fn: (model: ReturnType", _jsxs(M, { children: [") => SM, elemPropsHook: BehaviorHook", _jsx(SM, {}), ", O> ): BehaviorHook", _jsx(ReturnType, {}), ", O> => ", , "return ((model: ReturnType", _jsxs(M, { children: [", props: any, ref: React.Ref", _jsxs("unknown", { children: [") => ", , "return elemPropsHook(fn(model), props, ref); }) as BehaviorHook", _jsx(ReturnType, {}), ", O>; }; } /** Simplify and speed up inference by capturing types in the signature itself */ interface BaseHook", _jsx(M, { extends: true, Model: true }), _jsx("any", {}), ", any>, O extends ", "> ",
/**
* Capture the model type in TypeScript only. Do not use in runtime!
*
* @private
*/
__model, ": M; /** * Capture the hook's output type in TypeScript only. Do not use in runtime! This is used to cache * and speed up the output types during inference * * @private */ __output: O; } // TypeScript function parameters are contravariant while return types are covariant. This is a // problem when someone hands us a model that correctly extends `Model", _jsx("any", {}), ", any>`, but adds extra // properties to the model. So `M extends Model", _jsx("any", {}), ", any>`. But the `BehaviorHook` is the return // type which will reverse the direction which is no longer true: `Model", _jsx("any", {}), ", any> extends M`. In // order to avoid this issue, we use the `bivarianceHack` found in ReactJS type definitions. This // hack forces Typescript to treat `M` as a bivariant allowing extension to go either direction. // Normally this would be less type safe, but we're using a generic `M` as a placeholder so there // isn't a real issue. Not 100% this is a bug, but the \"hack\" is a bit messy. // https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance // https://stackoverflow.com/questions/52667959/what-is-the-purpose-of-bivariancehack-in-typescript-types/52668133 /** * A BehaviorHook is a React hook that takes a model, elemProps, and a ref and returns props and * attributes to apply to an element or component. */ export interface BehaviorHook", _jsx(M, { extends: true, Model: true }), _jsx("any", {}), ", any>, O extends ", "> extends BaseHook", _jsx(M, {}), ", O> ", (model, elemProps, ref) => , "; } function setRef", _jsxs(T, { children: ["(ref: React.Ref", _jsxs(T, { children: [" | undefined, value: T): void ", , "if (ref) ", , "if (typeof ref === 'function') ", ref(value), "; } else ",
// Refs are readonly, but we can technically write to it without issue
ref.current = value, "; } } } /** * This function will create a new forked ref out of two input Refs. This is useful for components * that use `React.forwardRef`, but also need internal access to a Ref. * * This function is inspired by https://www.npmjs.com/package/@rooks/use-fork-ref * * @example * React.forwardRef((props, ref) => ",
*
// Returns a RefObject with a `current` property
* , " const myRef = React.useRef(ref) * * // Returns a forked Ref function to pass to an element. * // This forked ref will update both `myRef` and `ref` when React updates the element ref * const elementRef = useForkRef(ref, myRef) * * useEffect(() => ",
* console.log(myRef.current) // `current` is the DOM instance
*
// `ref` might be null since it depends on if someone passed a `ref` to your component
*
// `elementRef` is a function and we cannot get a current value out of it
* , ") * * return ", _jsx("div", { ref: elementRef }), "* }) */ export function useForkRef", _jsxs(T, { children: ["(ref1?: React.Ref", _jsxs(T, { children: [", ref2?: React.Ref", _jsxs(T, { children: ["): React.RefCallback", _jsxs(T, { children: [" ", , "return (value: T) => ", setRef(ref1, value), "; setRef(ref2, value); }; } /** * This functions handles the common use case where a component needs a local ref and needs to * forward a ref to an element. * @param ref The React ref passed from the `createComponent` factory function * * @example * const MyComponent = (", (children, ), " ...elemProps}: MyProps, ref, Element) => ",
* , " const ", (localRef, elementRef), " = useLocalRef(ref); * * // do something with `localRef` which is a `RefObject` with a `current` property * * return ", _jsx(Element, { ref: elementRef, ...elemProps }), "* } */ export function useLocalRef", _jsxs(T, { children: ["(ref?: React.Ref", _jsxs(T, { children: [") ", , "const localRef = React.useRef", _jsxs(T, { children: ["(null); const elementRef = useForkRef(ref, localRef); return ", (localRef, elementRef), "; } /** * Returns a model, or calls the model hook with config. Clever way around the conditional React * hook ESLint rule. * @param model A model, if provided * @param config Config for a model * @param modelHook A model hook that takes valid config * @example * const ContainerComponent = (", (children, model, ), " ...config}: ContainerProps) => ",
* , " const value = useDefaultModel(model, config, useContainerModel); * * // ... * } */ export function useDefaultModel", _jsx(T, {}), ", C>( model: T | undefined, config: C, modelHook: (config: C) => T, as?: React.ElementType ) ",
// Make sure we don't pass the `model` to a component if it is incompatible with that component.
// Otherwise we'll have strange runtime failures when a component or elemProps hooks try to
// access the `state` or `events`
, "// Make sure we don't pass the `model` to a component if it is incompatible with that component. // Otherwise we'll have strange runtime failures when a component or elemProps hooks try to // access the `state` or `events` if ( !model || (as && (as as any).__hasModel && (model as any).__UNSTABLE_modelContext !== (modelHook as any).Context) ) ", , "return modelHook(config); } return model; } /** * Returns a model, or returns a model context. Clever way around the conditional React hook ESLint * rule * @param model A model, if provided * @param context The context of a model * @example * const SubComponent = (", (children, model, ), " ...elemProps}: SubComponentProps, ref, Element) => ",
* , " const ", (state, events), " = useModelContext(model, SubComponentModelContext, Element); * * // ... * } */ export function useModelContext", _jsxs(T, { children: ["( context: React.Context", _jsxs(T, { children: [", model?: T, as?: React.ElementType ): T ", , "const contextModel = React.useContext(context); if ( !model || (as && (as as any).__hasModel && (model as any).__UNSTABLE_modelContext !== context) ) ", , "return contextModel; } return model; } /** * Compose many hooks together. Each hook should make a call to `mergeProps` which is automatically * done by `createElemPropsHook` and `createHook. Returns a function that will receive a model and * return props to be applied to a component. Hooks run from last to first, but props override from * first to last. This means the last hook will run first, passing `elemProps` to the next last * hook. There is a special exception, which is `null`. `null` means \"remove this prop\" and the null * handling takes precedence to the first. Take care when using `null` as it will remove props * passed in even from the developer. It can be useful when passing data between composed hooks or * then redirecting a prop somewhere else. * * For example: * * ```ts * const useHook1 = createElemPropsHook(useMyModel)((model, ref, elemProps) => ",
* console.log('useHook1', elemProps)
* , " return ",
* a, ": 'useHook1', * c: 'useHook1', * d: null, // remove the `d` prop * } * }) * * const useHook2 = createElemPropsHook(useMyModel)((model, ref, elemProps) => ",
* console.log('useHook2', elemProps)
* , " return ",
* b, ": 'useHook2', * c: 'useHook2', * d: 'useHook2', * } * }) * * const useHook3 = composeHooks( * useHook1, // run last, will have access to `useHook2`'s elemProps, but can remove a prop with `null` * useHook2 // run first and will override all of `useHook1`'s props * ) * const props = useHook3(model, ", c, ": 'props', d: 'props' }) * console.log('props', props) * ``` * * The output would be: * * ```ts * useHook2 ", c, ": 'props', d: 'props'} * useHook1 ", b, ": 'useHook2', c: 'props', d: 'props'} * props ", a, ": 'useHook1', b: 'useHook2', c: 'props', d: null} * ``` */ export function composeHooks", _jsx(H1, { extends: true, BaseHook: true }), _jsx("any", {}), ", ", ">, H2 extends BaseHook", _jsx("any", {}), ", ", ">, H3 extends BaseHook", _jsx("any", {}), ", ", ">, H4 extends BaseHook", _jsx("any", {}), ", ", ">, H5 extends BaseHook", _jsx("any", {}), ", ", ">, H6 extends BaseHook", _jsx("any", {}), ", ", ">, H7 extends BaseHook", _jsx("any", {}), ", ", ">, >( hook1: H1, hook2: H2, hook3?: H3, hook4?: H4, hook5?: H5, hook6?: H6, hook7?: H7, // TypeScript will only infer up to 6, but the types will still exist for those 6. The rest of the // hooks won't add to the interface, but that seems to be an okay fallback ...hooks: BehaviorHook", _jsx("any", {}), ", any>[] ): H1 extends BaseHook", _jsx("infer", { M: true }), ", infer O1> ? H2 extends BaseHook", _jsx("any", {}), ", infer O2> ? H3 extends BaseHook", _jsx("any", {}), ", infer O3> ? H4 extends BaseHook", _jsx("any", {}), ", infer O4> ? H5 extends BaseHook", _jsx("any", {}), ", infer O5> ? H6 extends BaseHook", _jsx("any", {}), ", infer O6> ? H7 extends BaseHook", _jsx("any", {}), ", infer O7> ? BehaviorHook", _jsx(M, {}), ", RemoveNulls", _jsxs(MergeProps, { children: ["> : never : never : never : never : never : never : never; export function composeHooks", _jsx(M, { extends: true, Model: true }), _jsx("any", {}), ", any>, P extends ", ", O extends ", ">( ...hooks: ((model: M, props: P, ref: React.Ref", _jsxs("unknown", { children: [") => O)[] ): BehaviorHook", _jsx(M, {}), ", O> ", , "return ((model, props, ref) => ", , "const returnProps = [...hooks].reverse().reduce((props: any, hook) => ", , "return hook(model, props, props.ref || ref); }, props); // remove null props values for (const key in returnProps) ", , "if (returnProps[key] === null) ", delete returnProps[key], "; } } if (!returnProps.hasOwnProperty('ref') && ref) ",
// This is the weird "incoming ref isn't in props, but outgoing ref is in props" thing
returnProps.ref = ref, "; } return returnProps; }) as BehaviorHook", _jsx(M, {}), ", O>; }"] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] })] });
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment