Skip to content

Instantly share code, notes, and snippets.

@7iomka
Forked from Kelin2025/withease-factories-react.tsx
Created September 6, 2023 02:20
Show Gist options
  • Save 7iomka/557a7710a6165db288bd2b73624201e0 to your computer and use it in GitHub Desktop.
Save 7iomka/557a7710a6165db288bd2b73624201e0 to your computer and use it in GitHub Desktop.
// CAUTION
// - Avoid dynamic factories calls
// - Generic factories are not supported in `useModel`and `modelView` components
// Usage:
// const Root = modelView(factory, () => { ... })
// const model = useModel(factory)
// ... const $$instance = invoke(createSome, {someParams})
'use client';
import { createFactory } from '@withease/factories';
import type { invoke } from '@withease/factories';
import type { ComponentType, Context, Provider } from 'react';
import { createContext, useContext } from 'react';
const contexts = new Map<
ReturnType<typeof createFactory>,
Context<ReturnType<typeof invoke<ReturnType<typeof createFactory>>>>
>();
export const createModelProvider = <T extends (props: any) => any>(factory: T) => {
contexts.set(factory, createContext(null));
return contexts.get(factory)!.Provider as Provider<ReturnType<typeof invoke<T>>>;
};
export const useModel = <T extends (props: any) => any>(factory: T) => {
const model = useContext(contexts.get(factory)!);
if (!model) {
throw new Error('No model found');
}
return model as ReturnType<T>;
};
// Helper type for model prop
export type FactoryModelType<T extends (props: any) => any> = ReturnType<typeof invoke<T, any>>;
// declare function invoke<C extends (...args: any) => any>(factory: C): OverloadReturn<void, OverloadUnion<C>>;
// declare function invoke<C extends (...args: any) => any, P extends OverloadParameters<C>[0]>(factory: C, params: P): OverloadReturn<P, OverloadUnion<C>>;
/**
* HOC that wraps your `View` into model `Provider`. Also adds `model` prop that will be passed into `Provider`
* @param factory Factory that will be passed through Context
* @param View Root component that will be wrapped into Context
* @returns Wrapped component
*/
export const modelView = <T extends (props: any) => any, Props extends object = object>(
factory: T,
View: ComponentType<Props>,
) => {
const Provider = createModelProvider(factory);
// TODO: Find a workaround to use FactoryModelType as model type without breaking generics
const Render = ({ model, ...restProps }: Props & { model: any }) => {
return (
<Provider value={model}>
<View {...(restProps as Props)} />
</Provider>
);
};
// `as` is used for a better "Go To Definition"
return Render;
};
export { createFactory };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment