Skip to content

Commit 6de512e

Browse files
authored
Merge pull request #157 from rawagner/hooks_manager_eq
fix: ensure RemoteHookManager returns referencially equal results if …
2 parents 3ef9cc4 + 3822201 commit 6de512e

File tree

10 files changed

+305
-162
lines changed

10 files changed

+305
-162
lines changed

examples/test-app/src/routes/RemoteHookManager.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ function isTimerResult(result: any): result is UseRemoteHookResult<TimerResult>
4545
}
4646

4747
const RemoteHookManager = () => {
48-
const manager = useRemoteHookManager();
48+
const { addHook, cleanup, hookResults } = useRemoteHookManager();
4949
const [hooks, setHooks] = useState<any[]>([]);
5050

5151
// Add counter hook
5252
const addCounterHook = () => {
53-
const handle = manager.addHook({
53+
const handle = addHook({
5454
scope: 'sdk-plugin',
5555
module: './useCounterHook',
5656
args: [{ initialValue: Math.floor(Math.random() * 10), step: 1 }],
@@ -61,7 +61,7 @@ const RemoteHookManager = () => {
6161

6262
// Add API hook
6363
const addApiHook = () => {
64-
const handle = manager.addHook({
64+
const handle = addHook({
6565
scope: 'sdk-plugin',
6666
module: './useApiHook',
6767
args: [
@@ -81,7 +81,7 @@ const RemoteHookManager = () => {
8181

8282
// Add timer hook
8383
const addTimerHook = () => {
84-
const handle = manager.addHook({
84+
const handle = addHook({
8585
scope: 'sdk-plugin',
8686
module: './useTimerHook',
8787
args: [{ duration: 10, autoStart: true }],
@@ -105,15 +105,12 @@ const RemoteHookManager = () => {
105105
}
106106
};
107107

108-
// Get all hook results
109-
const hookResults = manager.getHookResults();
110-
111108
// Cleanup on unmount
112109
useEffect(() => {
113110
return () => {
114-
manager.cleanup();
111+
cleanup();
115112
};
116-
}, [manager]);
113+
}, [cleanup]);
117114

118115
return (
119116
<Grid container spacing={4}>
@@ -146,7 +143,7 @@ const RemoteHookManager = () => {
146143
<Button
147144
variant="outlined"
148145
onClick={() => {
149-
manager.cleanup();
146+
cleanup();
150147
setHooks([]);
151148
}}
152149
color="error"

packages/react-core/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ function DynamicHooksComponent() {
302302
// handle.remove();
303303
};
304304

305-
const results = manager.getHookResults();
305+
const results = manager.hookResults;
306306

307307
return (
308308
<div>
@@ -323,7 +323,7 @@ function DynamicHooksComponent() {
323323
**Methods:**
324324
- `addHook(config)` - Add a new remote hook, returns handle
325325
- `cleanup()` - Remove all hooks (called automatically on unmount)
326-
- `getHookResults()` - Get results from all tracked hooks
326+
- `hookResults` - Results from all tracked hooks
327327

328328
**Handle Methods:**
329329
- `remove()` - Remove this specific hook

packages/react-core/docs/remote-hook-types.md

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const config: HookConfig = {
6363

6464
### UseRemoteHookResult<T>
6565

66-
Result object returned by `useRemoteHook` and included in `getHookResults()` arrays.
66+
Result object returned by `useRemoteHook` and included in `hookResults` arrays.
6767

6868
```tsx
6969
interface UseRemoteHookResult<T> {
@@ -153,14 +153,14 @@ Interface for the hook manager returned by `useRemoteHookManager()`.
153153
interface RemoteHookManager {
154154
addHook(config: HookConfig): HookHandle;
155155
cleanup(): void;
156-
getHookResults(): UseRemoteHookResult<any>[];
156+
hookResults: UseRemoteHookResult<any>[];
157157
}
158158
```
159159

160160
**Methods:**
161161
- `addHook(config)`: Add a new remote hook and return a handle
162162
- `cleanup()`: Remove all managed hooks and clean up resources
163-
- `getHookResults()`: Get an array of all current hook results
163+
- `hookResults`: An array of all current hook results
164164

165165
**Example:**
166166
```tsx
@@ -176,11 +176,7 @@ function useHookManager() {
176176
return handle;
177177
};
178178

179-
const getAllResults = (): UseRemoteHookResult<any>[] => {
180-
return manager.getHookResults();
181-
};
182-
183-
return { addCounter, getAllResults, cleanup: manager.cleanup };
179+
return { addCounter, hookResults: manager.hookResults, cleanup: manager.cleanup };
184180
}
185181
```
186182

@@ -269,13 +265,13 @@ function TypedHookManager() {
269265
});
270266
};
271267

272-
const getApiResults = (): UseRemoteHookResult<ApiHookResult>[] => {
273-
return manager.getHookResults().filter(result =>
268+
const apiResults = useMemo(() => {
269+
return manager.hookResults.filter(result =>
274270
result.hookResult && 'data' in result.hookResult
275271
) as UseRemoteHookResult<ApiHookResult>[];
276-
};
272+
}, [manager.hookResults]);
277273

278-
return { addApiHook, getApiResults };
274+
return { addApiHook, apiResults };
279275
}
280276
```
281277

@@ -355,7 +351,7 @@ interface MyHooks {
355351

356352
function useTypedHookRegistry(): HookRegistry<MyHooks> {
357353
const manager = useRemoteHookManager();
358-
const results = manager.getHookResults();
354+
const results = manager.hookResults;
359355

360356
// Implementation would map results to the typed registry
361357
return {} as HookRegistry<MyHooks>;

packages/react-core/docs/use-remote-hook-manager.md

Lines changed: 20 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ handle.updateArgs([{ config: 'newValue' }]);
2525
handle.remove();
2626

2727
// Get all hook results
28-
const results = manager.getHookResults();
28+
const results = manager.hookResults;
2929

3030
// Clean up all hooks (do this on unmount)
31-
useEffect(() => () => manager.cleanup(), [manager]);
31+
useEffect(() => () => manager.cleanup(), [manager.cleanup]);
3232
```
3333

3434
**When to use:** Managing multiple remote hooks dynamically, adding/removing hooks at runtime, or building plugin systems.
@@ -48,10 +48,10 @@ import { useRemoteHookManager } from '@scalprum/react-core';
4848
import { useEffect } from 'react';
4949

5050
function HookManagerComponent() {
51-
const manager = useRemoteHookManager();
51+
const { addHook, cleanup, hookResults } = useRemoteHookManager();
5252

5353
const addCounterHook = () => {
54-
const handle = manager.addHook({
54+
const handle = addHook({
5555
scope: 'counter-app',
5656
module: './useCounter',
5757
args: [{ initialValue: 0, step: 1 }]
@@ -62,18 +62,17 @@ function HookManagerComponent() {
6262
};
6363

6464
const getAllResults = () => {
65-
const results = manager.getHookResults();
66-
console.log('All hook results:', results);
65+
console.log('All hook results:', hookResults);
6766
};
6867

6968
const cleanupAll = () => {
70-
manager.cleanup();
69+
cleanup();
7170
};
7271

7372
// Cleanup on unmount
7473
useEffect(() => {
75-
return () => manager.cleanup();
76-
}, [manager]);
74+
return () => cleanup();
75+
}, [cleanup]);
7776

7877
return (
7978
<div>
@@ -108,9 +107,9 @@ Adds a new remote hook and returns a handle to control it.
108107
- `remove()`: Remove this specific hook
109108
- `updateArgs(args: any[])`: Update arguments for this hook
110109

111-
#### `getHookResults(): UseRemoteHookResult<any>[]`
110+
#### `hookResults: UseRemoteHookResult<any>[]`
112111

113-
Returns an array of all current hook results.
112+
An array of all current hook results.
114113

115114
**Returns:** Array of `UseRemoteHookResult` objects with:
116115
- `id`: Unique hook identifier
@@ -170,16 +169,12 @@ function MultiHookManager() {
170169
setHooks(prev => prev.filter((_, i) => i !== index));
171170
};
172171

173-
const getAllResults = () => {
174-
return manager.getHookResults();
175-
};
176-
177172
// Cleanup on unmount
178173
useEffect(() => {
179174
return () => manager.cleanup();
180-
}, [manager]);
175+
}, [manager.cleanup]);
181176

182-
const hookResults = getAllResults();
177+
const { hookResults } = manager;
183178

184179
return (
185180
<div>
@@ -237,7 +232,7 @@ function DynamicArgsManager() {
237232
setHandles(prev => prev.filter((_, i) => i !== index));
238233
};
239234

240-
const hookResults = manager.getHookResults();
235+
const { hookResults } = manager;
241236

242237
return (
243238
<div>
@@ -287,7 +282,7 @@ function HookResultsProcessor() {
287282

288283
useEffect(() => {
289284
// Process hook results whenever they change
290-
const results = manager.getHookResults();
285+
const results = manager.hookResults;
291286

292287
const processed = results.reduce((acc, result, index) => {
293288
if (!result.loading && !result.error && result.hookResult) {
@@ -302,7 +297,7 @@ function HookResultsProcessor() {
302297
}, {});
303298

304299
setProcessedData(processed);
305-
}, [manager]); // Re-run when results change
300+
}, [manager.hookResults]); // Re-run when results change
306301

307302
const determineHookType = (hookResult) => {
308303
if (typeof hookResult.count === 'number') return 'counter';
@@ -380,7 +375,6 @@ function MemoizedArgsExample() {
380375
## Performance Considerations
381376

382377
- **Manager Stability**: The manager object is stable across re-renders
383-
- **Hook Results**: Call `getHookResults()` to get fresh results, as they're not automatically reactive
384378
- **Memory Management**: Always call `cleanup()` or individual `remove()` methods to prevent memory leaks
385379
- **Argument Updates**: Use `handle.updateArgs()` to update arguments instead of removing and re-adding hooks
386380

@@ -414,31 +408,11 @@ function MemoizedArgsExample() {
414408

415409
```tsx
416410
function ErrorHandlingManager() {
417-
const manager = useRemoteHookManager();
418-
const [errors, setErrors] = useState([]);
419-
420-
const addHookWithErrorHandling = (config) => {
421-
try {
422-
const handle = manager.addHook(config);
423-
424-
// Check results periodically for errors
425-
setTimeout(() => {
426-
const results = manager.getHookResults();
427-
const newErrors = results
428-
.filter(result => result.error)
429-
.map(result => ({ id: result.id, error: result.error.message }));
430-
431-
if (newErrors.length > 0) {
432-
setErrors(prev => [...prev, ...newErrors]);
433-
}
434-
}, 1000);
435-
436-
return handle;
437-
} catch (error) {
438-
console.error('Failed to add hook:', error);
439-
setErrors(prev => [...prev, { id: 'add-hook', error: error.message }]);
440-
}
441-
};
411+
const { hookResults } = useRemoteHookManager();
412+
413+
const errors = hookResults
414+
.filter(result => result.error)
415+
.map(result => ({ id: result.id, error: result.error.message }));
442416

443417
return (
444418
<div>

packages/react-core/jest.config.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ export default {
33
displayName: '@scalprum/react-core',
44
preset: '../../jest.preset.js',
55
transform: {
6-
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
6+
'^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
77
},
8-
moduleFileExtensions: ['ts', 'js', 'html'],
8+
moduleNameMapper: {
9+
'^@scalprum/core$': '<rootDir>/../core/src/index.ts',
10+
},
11+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
912
coverageDirectory: '../../coverage/packages/react-core',
13+
testEnvironment: 'jsdom',
14+
testMatch: ['**/*.test.ts?(x)', '**/*.spec.ts?(x)'],
1015
};

packages/react-core/src/remote-hooks-types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ export interface HookHandle {
2929
updateArgs(args: any[]): void;
3030
}
3131

32-
export interface RemoteHookManager {
32+
export interface RemoteHookManager<R = unknown> {
3333
addHook(config: HookConfig): HookHandle; // Returns handle with remove and updateArgs
3434
cleanup(): void; // Cleanup for component unmount
35-
getHookResults(): UseRemoteHookResult<any>[]; // Get results for all tracked hooks
35+
hookResults: UseRemoteHookResult<R>[]; // Results for all tracked hooks
3636
}
3737

3838
// Context type from RemoteHookProvider

0 commit comments

Comments
 (0)