Skip to content

Commit

Permalink
improvement: bind hooks' callbacks to a scope (#82)
Browse files Browse the repository at this point in the history
* fix: function and variable declarations to be more correct

* test: hooks calls in a scope

* improvement: unify a way to declare type

* improvement: bind hooks' callbacks to be fired in a scope

---------

Co-authored-by: ivan posokhin <[email protected]>
  • Loading branch information
iposokhin and ivan posokhin authored Jan 24, 2024
1 parent e6bc134 commit 0341842
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 38 deletions.
57 changes: 21 additions & 36 deletions src/core/reflect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ export function reflectFactory(context: Context) {
return function reflect<Props, Bind extends BindProps<Props> = BindProps<Props>>(
config: ReflectConfig<Props, Bind>,
): React.ExoticComponent<{}> {
const { stores, events, data, functions } = sortProps(config);
const { stores, events, data, functions } = sortProps(config.bind);
const hooks = sortProps(config.hooks || {});

return React.forwardRef((props, ref) => {
const storeProps = context.useUnit(stores, config.useUnitConfig);
const eventsProps = context.useUnit(events as any, config.useUnitConfig);
const functionProps = useBindedFunctions(functions);
const functionProps = useBoundFunctions(functions);

const elementProps: Props = Object.assign(
{ ref },
Expand All @@ -42,42 +43,38 @@ export function reflectFactory(context: Context) {
props,
);

const mounted = wrapToHook(
config.hooks?.mounted,
context,
config.useUnitConfig,
);
const unmounted = wrapToHook(
config.hooks?.unmounted,
context,
config.useUnitConfig,
);
const eventsHooks = context.useUnit(hooks.events as any, config.useUnitConfig);
const functionsHooks = useBoundFunctions(hooks.functions);

React.useEffect(() => {
if (mounted) mounted();
const hooks: Hooks = Object.assign({}, functionsHooks, eventsHooks);

if (hooks.mounted) {
hooks.mounted();
}

return () => {
if (unmounted) unmounted();
if (hooks.unmounted) {
hooks.unmounted();
}
};
}, []);
}, [eventsHooks, functionsHooks]);

return React.createElement(config.view as any, elementProps as any);
});
};
}

function sortProps<Props, Bind extends BindProps<Props> = BindProps<Props>>(
config: ReflectConfig<Props, Bind>,
) {
function sortProps<T extends object>(props: T) {
type GenericEvent = Event<unknown> | Effect<unknown, unknown, unknown>;

const events: Record<string, GenericEvent> = {};
const stores: Record<string, Store<unknown>> = {};
const data: Record<string, unknown> = {};
const functions: Record<string, Function> = {};

for (const key in config.bind) {
const value = config.bind[key];
for (const key in props) {
const value = props[key];

if (is.event(value) || is.effect(value)) {
events[key] = value;
Expand All @@ -93,30 +90,18 @@ function sortProps<Props, Bind extends BindProps<Props> = BindProps<Props>>(
return { events, stores, data, functions };
}

function useBindedFunctions(functions: Record<string, Function>) {
function useBoundFunctions(functions: Record<string, Function>) {
const scope = useProvidedScope();

return React.useMemo(() => {
const bindedFunctions: Record<string, Function> = {};
const boundFunctions: Record<string, Function> = {};

for (const key in functions) {
const fn = functions[key];

bindedFunctions[key] = scopeBind(fn, { scope: scope || undefined, safe: true });
boundFunctions[key] = scopeBind(fn, { scope: scope || undefined, safe: true });
}

return bindedFunctions;
return boundFunctions;
}, [scope, functions]);
}

function wrapToHook(hook: Hook | void, context: Context, config?: UseUnitConifg) {
if (hookDefined(hook)) {
return context.useUnit(hook as EventCallable<void>, config);
}

return hook;
}

function hookDefined(hook: Hook | void): hook is Hook {
return Boolean(hook && (is.event(hook) || is.effect(hook)));
}
4 changes: 2 additions & 2 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ export type BindProps<Props> = {

export type Hook = (() => any) | EventCallable<void> | Effect<void, any, any>;

export interface Hooks {
export type Hooks = {
mounted?: Hook;
unmounted?: Hook;
}
};

export type UseUnitConifg = Parameters<typeof useUnit>[1];
44 changes: 44 additions & 0 deletions src/no-ssr/reflect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,28 @@ describe('hooks', () => {
expect(mounted.mock.calls.length).toBe(1);
});

test('callback in scope', () => {
const mounted = createEvent();
const $isMounted = createStore(false).on(mounted, () => true);

const scope = fork();

const Name = reflect({
view: InputBase,
bind: {},
hooks: { mounted: () => mounted() },
});

render(
<Provider value={scope}>
<Name data-testid="name" />
</Provider>,
);

expect($isMounted.getState()).toBe(false);
expect(scope.getState($isMounted)).toBe(true);
});

test('event', () => {
const changeName = createEvent<string>();
const $name = restore(changeName, '');
Expand All @@ -339,6 +361,28 @@ describe('hooks', () => {

expect(fn.mock.calls.length).toBe(1);
});

test('event in scope', () => {
const mounted = createEvent();
const $isMounted = createStore(false).on(mounted, () => true);

const scope = fork();

const Name = reflect({
view: InputBase,
bind: {},
hooks: { mounted },
});

render(
<Provider value={scope}>
<Name data-testid="name" />
</Provider>,
);

expect($isMounted.getState()).toBe(false);
expect(scope.getState($isMounted)).toBe(true);
});
});

describe('unmounted', () => {
Expand Down

0 comments on commit 0341842

Please sign in to comment.