Skip to content

Instantly share code, notes, and snippets.

@dovranJorayev
Created May 2, 2024 08:22
Show Gist options
  • Save dovranJorayev/0f28f53c460c2f6653b1b44c9aad2bd2 to your computer and use it in GitHub Desktop.
Save dovranJorayev/0f28f53c460c2f6653b1b44c9aad2bd2 to your computer and use it in GitHub Desktop.
effector model react bindings
import {
ComponentType,
ForwardRefExoticComponent,
PropsWithoutRef,
RefAttributes,
createContext,
forwardRef,
useContext
} from 'react';
export type ComponentLike<P> = (props: P) => JSX.Element | null;
/**
* 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 isForwardRefComponent<P>(
component: ComponentLike<P> | ComponentType<P> | ForwardRefExoticComponent<P>
): component is ForwardRefExoticComponent<P> {
return (
(component as ForwardRefExoticComponent<P>).$$typeof ===
Symbol.for('react.forward_ref')
);
}
export function makeModelContext<M extends object = object>(
createModel?: (...params: any[]) => M,
modelName = 'AnonymousModel'
) {
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
};
}
@dovranJorayev
Copy link
Author

dovranJorayev commented May 2, 2024

// widgets/my-widget/lib.ts
export const ctx = makeModelContext(createMyWidgetModel);  
// or  makeModelContext<MyWidgetModel>(); 


// widgets/my-widget/ui/root.tsx
const MyWidgetRoot = ctx.withProvider(
 (
   props: MyWidgetRootOwnProps
 ) => {
     return (
        <div>
            <MyWidgetPartA/> // using inside ctx.useModel()
            <MyWidgetPartB/> // using inside ctx.useModel()
         </div>
     )
});

// widgets/my-widget/ui/part-a.tsx
export const MyWidgetPartA = () => {
    const model = ctx.useModel();
    
    return ....
}


// pages/some/ui.tsx
export const SomePage = () => {
    
    return (
       <MyWidgetRoot
            a='1' 
            b={false} 
            // i don't care how model is created
            model={pageModel.myWidgetModel}
        />
     );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment