Skip to content
This repository was archived by the owner on Oct 16, 2024. It is now read-only.

Commit 0e73f5c

Browse files
committed
split useAgile hook
1 parent d0e6435 commit 0e73f5c

File tree

6 files changed

+302
-203
lines changed

6 files changed

+302
-203
lines changed

packages/core/src/runtime/subscription/sub.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ export class SubController {
331331
}
332332
}
333333

334-
interface RegisterSubscriptionConfigInterface
334+
export interface RegisterSubscriptionConfigInterface
335335
extends SubscriptionContainerConfigInterface {
336336
/**
337337
* Whether the Subscription Container shouldn't be ready

packages/react/src/hooks/useAgile.ts

Lines changed: 31 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,18 @@
1-
import React from 'react';
2-
import Agile, {
3-
getAgileInstance,
1+
import {
42
Observer,
53
State,
6-
SubscriptionContainerKeyType,
7-
isValidObject,
84
generateId,
9-
ProxyWeakMapType,
10-
ComponentIdType,
115
extractRelevantObservers,
12-
SelectorWeakMapType,
13-
SelectorMethodType,
14-
LogCodeManager,
156
normalizeArray,
167
defineConfig,
178
} from '@agile-ts/core';
189
import type { Collection, Group } from '@agile-ts/core'; // Only import Collection and Group type for better Treeshaking
19-
import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
20-
21-
// TODO https://stackoverflow.com/questions/68148235/require-module-inside-a-function-doesnt-work
22-
let proxyPackage: any = null;
23-
try {
24-
proxyPackage = require('@agile-ts/proxytree');
25-
} catch (e) {
26-
// empty catch block
27-
}
10+
import {
11+
BaseAgileHookConfigInterface,
12+
getReturnValue,
13+
SubscribableAgileInstancesType,
14+
useBaseAgile,
15+
} from './useBaseAgile';
2816

2917
/**
3018
* A React Hook for binding the most relevant value of multiple Agile Instances
@@ -67,162 +55,38 @@ export function useAgile<
6755
): AgileOutputHookArrayType<X> | AgileOutputHookType<Y> {
6856
config = defineConfig(config, {
6957
key: generateId(),
70-
proxyBased: false,
7158
agileInstance: null as any,
7259
componentId: undefined,
7360
observerType: undefined,
7461
deps: [],
62+
handleReturn: (dep: Observer | undefined) => {
63+
return dep != null ? dep.value : undefined;
64+
},
7565
});
7666
const depsArray = extractRelevantObservers(
7767
normalizeArray(deps),
7868
config.observerType
7969
);
80-
const proxyTreeWeakMap = new WeakMap();
81-
82-
// Builds return value,
83-
// depending on whether the deps were provided in array shape or not
84-
const getReturnValue = (
85-
depsArray: (Observer | undefined)[]
86-
): AgileOutputHookArrayType<X> | AgileOutputHookType<Y> => {
87-
const handleReturn = (
88-
dep: Observer | undefined
89-
): AgileOutputHookType<Y> => {
90-
if (dep == null) return undefined as any;
91-
const value = dep.value;
92-
93-
// If proxyBased and the value is of the type object.
94-
// Wrap a Proxy around the object to track the accessed properties.
95-
if (config.proxyBased && isValidObject(value, true)) {
96-
if (proxyPackage != null) {
97-
const { ProxyTree } = proxyPackage;
98-
const proxyTree = new ProxyTree(value);
99-
proxyTreeWeakMap.set(dep, proxyTree);
100-
return proxyTree.proxy;
101-
} else {
102-
console.error(
103-
'In order to use the Agile proxy functionality, ' +
104-
`the installation of an additional package called '@agile-ts/proxytree' is required!`
105-
);
106-
}
107-
}
108-
109-
// If specified selector function and the value is of type object.
110-
// Return the selected value.
111-
// (Destroys the type of the useAgile hook,
112-
// however the type can be adjusted in the useSelector hook)
113-
if (config.selector && isValidObject(value, true)) {
114-
return config.selector(value);
115-
}
116-
117-
return value;
118-
};
119-
120-
// Handle single dep return value
121-
if (depsArray.length === 1 && !Array.isArray(deps)) {
122-
return handleReturn(depsArray[0]);
123-
}
124-
125-
// Handle deps array return value
126-
return depsArray.map((dep) => {
127-
return handleReturn(dep);
128-
}) as AgileOutputHookArrayType<X>;
129-
};
130-
131-
// Trigger State, used to force Component to rerender
132-
const [, forceRender] = React.useReducer((s) => s + 1, 0);
133-
134-
useIsomorphicLayoutEffect(() => {
135-
let agileInstance = config.agileInstance;
136-
137-
// https://github.com/microsoft/TypeScript/issues/20812
138-
const observers: Observer[] = depsArray.filter(
139-
(dep): dep is Observer => dep !== undefined
140-
);
141-
142-
// Try to extract Agile Instance from the specified Instance/s
143-
if (!agileInstance) agileInstance = getAgileInstance(observers[0]);
144-
if (!agileInstance || !agileInstance.subController) {
145-
LogCodeManager.getLogger()?.error(
146-
'Failed to subscribe Component with deps because of missing valid Agile Instance.',
147-
deps
148-
);
149-
return;
150-
}
151-
152-
// TODO Proxy doesn't work as expected when 'selecting' a not yet existing property.
153-
// For example you select the 'user.data.name' property, but the 'user' object is undefined.
154-
// -> No correct Proxy Path could be created on the Component mount, since the to select property doesn't exist
155-
// -> Selector was created based on the not complete Proxy Path
156-
// -> Component re-renders to often
157-
//
158-
// Build Proxy Path WeakMap based on the Proxy Tree WeakMap
159-
// by extracting the routes from the Proxy Tree.
160-
// Building the Path WeakMap in the 'useIsomorphicLayoutEffect'
161-
// because the 'useIsomorphicLayoutEffect' is called after the rerender.
162-
// -> All used paths in the UI-Component were successfully tracked.
163-
let proxyWeakMap: ProxyWeakMapType | undefined = undefined;
164-
if (config.proxyBased && proxyPackage != null) {
165-
proxyWeakMap = new WeakMap();
166-
for (const observer of observers) {
167-
const proxyTree = proxyTreeWeakMap.get(observer);
168-
if (proxyTree != null) {
169-
proxyWeakMap.set(observer, {
170-
paths: proxyTree.getUsedRoutes() as any,
171-
});
172-
}
173-
}
174-
}
175-
176-
// Build Selector WeakMap based on the specified selector method
177-
let selectorWeakMap: SelectorWeakMapType | undefined = undefined;
178-
if (config.selector != null) {
179-
selectorWeakMap = new WeakMap();
180-
for (const observer of observers) {
181-
selectorWeakMap.set(observer, { methods: [config.selector] });
182-
}
183-
}
184-
185-
// Create Callback based Subscription
186-
const subscriptionContainer = agileInstance.subController.subscribe(
187-
() => {
188-
forceRender();
189-
},
190-
observers,
191-
{
192-
key: config.key,
193-
proxyWeakMap,
194-
waitForMount: false,
195-
componentId: config.componentId,
196-
selectorWeakMap,
197-
}
198-
);
19970

200-
// Unsubscribe Callback based Subscription on unmount
201-
return () => {
202-
agileInstance?.subController.unsubscribe(subscriptionContainer);
203-
};
204-
}, config.deps);
71+
useBaseAgile(
72+
depsArray,
73+
() => ({
74+
key: config.key,
75+
waitForMount: false,
76+
componentId: config.componentId,
77+
}),
78+
config.deps || [],
79+
config.agileInstance
80+
);
20581

206-
return getReturnValue(depsArray);
82+
return getReturnValue(
83+
depsArray,
84+
config.handleReturn as any,
85+
Array.isArray(deps)
86+
);
20787
}
20888

209-
export type SubscribableAgileInstancesType =
210-
| State
211-
| Collection<any, any> //https://stackoverflow.com/questions/66987727/type-classa-id-number-name-string-is-not-assignable-to-type-classar
212-
| Observer
213-
| undefined;
214-
215-
export interface AgileHookConfigInterface {
216-
/**
217-
* Key/Name identifier of the Subscription Container to be created.
218-
* @default undefined
219-
*/
220-
key?: SubscriptionContainerKeyType;
221-
/**
222-
* Instance of Agile the Subscription Container belongs to.
223-
* @default `undefined` if no Agile Instance could be extracted from the provided Instances.
224-
*/
225-
agileInstance?: Agile;
89+
export interface AgileHookConfigInterface extends BaseAgileHookConfigInterface {
22690
/**
22791
* Whether to wrap a [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
22892
* around the bound Agile Instance value object,
@@ -234,7 +98,7 @@ export interface AgileHookConfigInterface {
23498
*
23599
* @default false
236100
*/
237-
proxyBased?: boolean;
101+
// proxyBased?: boolean;
238102
/**
239103
* Equality comparison function
240104
* that allows you to customize the way the selected Agile Instance
@@ -245,12 +109,8 @@ export interface AgileHookConfigInterface {
245109
*
246110
* @default undefined
247111
*/
248-
selector?: SelectorMethodType;
249-
/**
250-
* Key/Name identifier of the UI-Component the Subscription Container is bound to.
251-
* @default undefined
252-
*/
253-
componentId?: ComponentIdType;
112+
// selector?: SelectorMethodType;
113+
254114
/**
255115
* What type of Observer to be bound to the UI-Component.
256116
*
@@ -261,14 +121,9 @@ export interface AgileHookConfigInterface {
261121
*/
262122
observerType?: string;
263123
/**
264-
* Dependencies that determine, in addition to unmounting and remounting the React-Component,
265-
* when the specified Agile Sub Instances should be re-subscribed to the React-Component.
266-
*
267-
* [Github issue](https://github.com/agile-ts/agile/issues/170)
268-
*
269-
* @default []
124+
* TODO
270125
*/
271-
deps?: any[];
126+
handleReturn?: (dep: Observer | undefined) => any;
272127
}
273128

274129
// Array Type
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import React from 'react';
2+
import Agile, {
3+
Collection,
4+
ComponentIdType,
5+
getAgileInstance,
6+
LogCodeManager,
7+
Observer,
8+
State,
9+
SubscriptionContainerKeyType,
10+
RegisterSubscriptionConfigInterface,
11+
} from '@agile-ts/core';
12+
import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect';
13+
14+
export const useBaseAgile = (
15+
depsArray: (Observer | undefined)[],
16+
getSubContainerConfig: (
17+
observers: Observer[]
18+
) => RegisterSubscriptionConfigInterface,
19+
deps: any[],
20+
agileInstance?: Agile
21+
) => {
22+
// Trigger State, used to force Component to rerender
23+
const [, forceRender] = React.useReducer((s) => s + 1, 0);
24+
25+
useIsomorphicLayoutEffect(() => {
26+
// https://github.com/microsoft/TypeScript/issues/20812
27+
const observers = depsArray.filter(
28+
(dep): dep is Observer => dep !== undefined
29+
);
30+
31+
const subContainerConfig = getSubContainerConfig(observers);
32+
33+
const _agileInstance = extractAgileInstance(observers, agileInstance);
34+
if (_agileInstance == null) return;
35+
36+
// Create Callback based Subscription
37+
const subscriptionContainer = _agileInstance.subController.subscribe(
38+
() => {
39+
forceRender();
40+
},
41+
observers,
42+
subContainerConfig
43+
);
44+
45+
// Unsubscribe Callback based Subscription on unmount
46+
return () => {
47+
_agileInstance.subController.unsubscribe(subscriptionContainer);
48+
};
49+
}, deps);
50+
};
51+
52+
export const extractAgileInstance = (
53+
observers: Observer[],
54+
agileInstance?: Agile
55+
): Agile | undefined => {
56+
if (agileInstance != null) return agileInstance;
57+
58+
// Try to extract Agile Instance from the specified Observers
59+
agileInstance = getAgileInstance(observers[0]);
60+
if (!agileInstance || !agileInstance.subController) {
61+
LogCodeManager.getLogger()?.error(
62+
'Failed to subscribe to React Component because of missing valid Agile Instance.',
63+
observers
64+
);
65+
return undefined;
66+
}
67+
return agileInstance;
68+
};
69+
70+
// Builds return value,
71+
// depending on whether the deps were provided in array shape or not
72+
export const getReturnValue = (
73+
depsArray: (Observer | undefined)[],
74+
handleReturn: (dep: Observer | undefined) => any,
75+
wasProvidedAsArray: boolean
76+
): any => {
77+
// Handle single dep return value
78+
if (depsArray.length === 1 && !wasProvidedAsArray) {
79+
return handleReturn(depsArray[0]);
80+
}
81+
82+
// Handle deps array return value
83+
return depsArray.map((dep) => {
84+
return handleReturn(dep);
85+
});
86+
};
87+
88+
export type SubscribableAgileInstancesType =
89+
| State
90+
| Collection<any, any> //https://stackoverflow.com/questions/66987727/type-classa-id-number-name-string-is-not-assignable-to-type-classar
91+
| Observer
92+
| undefined;
93+
94+
export interface BaseAgileHookConfigInterface {
95+
/**
96+
* Key/Name identifier of the Subscription Container to be created.
97+
* @default undefined
98+
*/
99+
key?: SubscriptionContainerKeyType;
100+
/**
101+
* Instance of Agile the Subscription Container belongs to.
102+
* @default `undefined` if no Agile Instance could be extracted from the provided Instances.
103+
*/
104+
agileInstance?: Agile;
105+
/**
106+
* Key/Name identifier of the UI-Component the Subscription Container is bound to.
107+
* @default undefined
108+
*/
109+
componentId?: ComponentIdType;
110+
/**
111+
* Dependencies that determine, in addition to unmounting and remounting the React-Component,
112+
* when the specified Agile Sub Instances should be re-subscribed to the React-Component.
113+
*
114+
* [Github issue](https://github.com/agile-ts/agile/issues/170)
115+
*
116+
* @default []
117+
*/
118+
deps?: any[];
119+
}

0 commit comments

Comments
 (0)