Created
January 19, 2024 17:23
-
-
Save dovranJorayev/84c744ba7b65a50fd667655dfd5485d4 to your computer and use it in GitHub Desktop.
View model boilerplate for factory models
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
ComponentType, | |
ForwardRefExoticComponent, | |
PropsWithoutRef, | |
RefAttributes, | |
createContext, | |
forwardRef, | |
useContext | |
} from 'react'; | |
// import { ComponentLike, isForwardRefComponent } from './react'; | |
// import { makeUnsafeDefaults } from './make-unsafe-defaults'; | |
export type ComponentLike<P> = (props: P) => JSX.Element | null; | |
export function isForwardRefComponent<P>( | |
component: ComponentLike<P> | ComponentType<P> | ForwardRefExoticComponent<P> | |
): component is ForwardRefExoticComponent<P> { | |
return ( | |
(component as ForwardRefExoticComponent<P>).$$typeof === | |
Symbol.for('react.forward_ref') | |
); | |
} | |
/** | |
* Used to create unsafe defaults for typescript types | |
* that need to be replaced with real values | |
* before the value was consumed | |
* | |
* @param error error message for trowed error | |
* @returns unsafe value | |
* @throws Error instance with error message that was passed as an argument | |
*/ | |
export const makeUnsafeDefaults = <T extends object>(error: string): T => { | |
return new Proxy<T>({} as never, { | |
get: () => { | |
throw new Error(error); | |
} | |
}); | |
}; | |
export function makeViewModel<M extends object = object>( | |
createModel: (...params: any[]) => M, | |
modelName: string = createModel.name | |
) { | |
const Ctx = createContext<M>( | |
makeUnsafeDefaults(`Model of ${modelName} instance was not provided`) | |
); | |
function withProvider<Props, Ref = never>( | |
Component: ComponentType<Props> | |
): ComponentType<Props & { model: M }>; | |
function withProvider<Props, Ref = never>( | |
Component: ComponentLike<Props> | |
): ComponentLike<Props & { model: M }>; | |
function withProvider<Props, Ref>( | |
Component: ForwardRefExoticComponent< | |
PropsWithoutRef<Props> & RefAttributes<Ref> | |
> | |
): ForwardRefExoticComponent< | |
PropsWithoutRef<Props & { model: M }> & RefAttributes<Ref> | |
>; | |
function withProvider<P extends { model: M }>( | |
ComponentLike: | |
| ComponentLike<P> | |
| ComponentType<P> | |
| ForwardRefExoticComponent<P> | |
): any { | |
if (isForwardRefComponent(ComponentLike)) { | |
return forwardRef(function ForwardRefWithModelProvider( | |
props: P, | |
ref: unknown | |
) { | |
return ( | |
<Ctx.Provider value={props.model}> | |
<ComponentLike {...props} ref={ref} /> | |
</Ctx.Provider> | |
); | |
}); | |
} else { | |
return function WithModelProvider(props: P) { | |
return ( | |
<Ctx.Provider value={props.model}> | |
<ComponentLike {...props} /> | |
</Ctx.Provider> | |
); | |
}; | |
} | |
} | |
function withModelInstance( | |
model: M, | |
displayName?: string | |
): <Props = Record<string, unknown>>( | |
Component: ComponentLike<Props> | |
) => ComponentLike<Props>; | |
function withModelInstance( | |
model: M, | |
displayName?: string | |
): <Props>(Component: ComponentType<Props>) => ComponentType<Props>; | |
function withModelInstance(model: M, displayName = 'ModelInjectedComponent') { | |
return <P, C extends ComponentLike<P> | ComponentType<P>>(Component: C) => { | |
const ModelInjectedComponent = (props: P) => { | |
return ( | |
<Ctx.Provider value={model}> | |
<Component {...(props as any)} /> | |
</Ctx.Provider> | |
); | |
}; | |
Object.assign(ModelInjectedComponent, { displayName }); | |
return ModelInjectedComponent; | |
}; | |
} | |
const useModel = () => useContext(Ctx); | |
return { | |
useModel, | |
withProvider, | |
withModelInstance | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment