Created
September 5, 2023 23:00
-
-
Save 7iomka/25f215a3cf41b0ec2c195b62de5495a8 to your computer and use it in GitHub Desktop.
Factory generic issue
This file contains 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
// 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); | |
const Render = ({ model, ...restProps }: Props & { model: FactoryModelType<T> }) => { | |
return ( | |
<Provider value={model}> | |
<View {...(restProps as Props)} /> | |
</Provider> | |
); | |
}; | |
// `as` is used for a better "Go To Definition" | |
return Render as ComponentType<Props & { model: ReturnType<T> }>; | |
}; | |
export { createFactory }; |
This file contains 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
<CreateSortingSelect | |
model={$$cartSorting} | |
useColumnLayoutOnMobile | |
/> | |
{/* We have issue here: */} |
This file contains 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
type ProductItemSortingType = | |
| 'assign_desc' | |
| 'assign_asc' | |
| 'subcategory_desc' | |
| 'subcategory_asc'; | |
const $$cartSorting = invoke(() => | |
createSortingModel<ProductItemSortingType>({ | |
sortingOptions: [ | |
{ label: 'По дате добавления ↑', value: 'assign_asc' }, | |
{ label: 'По дате добавления ↓', value: 'assign_desc' }, | |
{ label: 'По названию категории ↑', value: 'subcategory_asc' }, | |
{ label: 'По названию категории ↓', value: 'subcategory_desc' }, | |
], | |
defaultValue: 'assign_desc', | |
queryKey: 'sorting', | |
}), | |
); |
This file contains 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 { useUnit } from 'effector-react'; | |
import type { MantineNumberSize } from '@mantine/core'; | |
import { Select, useInlineOutsideSelectLabelStyles } from '@/shared/ui'; | |
import type { SelectProps } from '@/shared/ui'; | |
import { modelView, useModel } from '@/shared/lib/factory'; | |
import { factory } from '../sorting.model'; | |
interface CreateSortingSelectProps extends Omit<SelectProps, 'withinPortal' | 'data' | 'onChange'> { | |
className?: string; | |
labelSpacing?: MantineNumberSize; | |
useColumnLayoutOnMobile?: boolean; | |
} | |
export const CreateSortingSelect = modelView(factory, (props: CreateSortingSelectProps) => { | |
const { | |
className, | |
label = 'Сортировать', | |
hasFloatingLabel, | |
labelSpacing, | |
useColumnLayoutOnMobile, | |
...rest | |
} = props; | |
const model = useModel(factory); | |
const [onChange, value] = useUnit([model.valueChanged, model.$value]); | |
const options = model.sortingOptions; | |
const { classes } = useInlineOutsideSelectLabelStyles({ | |
size: props.size ?? 'md', | |
labelSpacing: props.labelSpacing, | |
useColumnLayoutOnMobile: props.useColumnLayoutOnMobile, | |
}); | |
return ( | |
<Select | |
className={className} | |
classNames={classes} | |
value={value} | |
onChange={(v: string) => onChange(v)} | |
data={options} | |
withinPortal | |
label={label} | |
hasFloatingLabel={hasFloatingLabel ?? false} | |
{...rest} | |
/> | |
); | |
}); |
This file contains 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 type { Event, Store } from 'effector'; | |
import { is, createStore, attach, createEvent, sample } from 'effector'; | |
import type { SelectItem } from '@mantine/core'; | |
import { createFactory } from '@withease/factories'; | |
import { $$navigation } from '@/entities/navigation'; | |
export type SortingOption<K extends string> = Omit<SelectItem, 'label' | 'value'> & { | |
label: string; | |
value: K; | |
}; | |
type FactoryOptions<K extends string = string> = { | |
sortingOptions: SortingOption<K>[]; | |
queryKey?: 'sorting' | string; | |
defaultValue?: SortingOption<K>['value'] | null; | |
getInitialValueOn?: Event<any | void> | Event<any | void>[]; | |
syncQueryParams?: boolean; | |
isEnabled?: Store<boolean> | boolean; | |
}; | |
export const factory = createFactory( | |
<K extends string>({ | |
sortingOptions, | |
queryKey = 'sorting', | |
defaultValue: _defaultValue = null, | |
getInitialValueOn, | |
syncQueryParams, | |
isEnabled, | |
}: FactoryOptions<K>) => { | |
const availableValues = sortingOptions.map((opt) => opt.value); | |
const $isEnabled = is.store(isEnabled) ? isEnabled : createStore(isEnabled ?? false); | |
// Allow null as default value, with optional fallback to `default` | |
const defaultValue = _defaultValue ?? ('default' in availableValues ? ('default' as K) : null); | |
// type SortingValue = (typeof sortingOptions)[number]['value']; | |
const initialValueSettled = createEvent<K | null>(); | |
const valueChanged = createEvent<K>(); | |
const urlPrepared = createEvent<{ url: string }>(); | |
const urlChangeRequested = createEvent<{ url: string }>(); | |
const urlChanged = createEvent<{ url: string }>(); | |
const changeUrlFx = attach({ effect: $$navigation.pushFx }); | |
// store for current sorting | |
const $value = createStore(defaultValue); | |
// Reflect value changes by init event | |
sample({ | |
clock: initialValueSettled, | |
target: $value, | |
}); | |
// Reflect value changes by event | |
sample({ | |
clock: valueChanged, | |
target: $value, | |
}); | |
/**/ | |
return { | |
sortingOptions, | |
queryKey, | |
defaultValue, | |
$value, | |
valueChanged, | |
urlChangeRequested, | |
urlChanged, | |
initialValueSettled, | |
}; | |
}, | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment