Skip to content

Commit

Permalink
fix(react-hooks): improve service and bundle lifecycle handling
Browse files Browse the repository at this point in the history
  • Loading branch information
noherczeg committed Mar 1, 2023
1 parent 2de1357 commit a722ed9
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 41 deletions.
19 changes: 17 additions & 2 deletions examples/react-systemjs/packages/app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import type { FC } from 'react';
import { useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { OBJECTCLASS } from '@pandino/pandino-api';
import { ComponentProxy } from '@pandino/react-hooks';
import { ComponentProxy, useTrackService } from '@pandino/react-hooks';
import { CUSTOM_COMPONENT_INTERFACE_KEY } from '@react-systemjs/component-api';
import { SOME_SERVICE_INTERFACE_KEY, SomeService } from './service';

export const App: FC = () => {
const [firstName] = useState<string>('John');
const { service: someService } = useTrackService<SomeService>(`(${OBJECTCLASS}=${SOME_SERVICE_INTERFACE_KEY})`);
const text = useMemo<string | undefined>(() => someService?.someMethod(), [someService]);

console.count('Rendering App.');

useEffect(() => {
console.info(`SomeService implementation: ${someService}`);
}, [someService]);

useEffect(() => {
console.info(`text: ${text}`);
}, [text]);

return (
<ComponentProxy filter={`(${OBJECTCLASS}=${CUSTOM_COMPONENT_INTERFACE_KEY})`} firstName={firstName}>
<div className={'fallback'}>fallback for: {firstName}</div>
<div className={'service'}>SomeService test: {someService?.someMethod()}</div>
<div className={'text'}>Text: {text}</div>
</ComponentProxy>
);
};
68 changes: 43 additions & 25 deletions examples/react-systemjs/packages/app/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { useState } from 'react';
import { createRoot } from 'react-dom/client';
import Pandino from '@pandino/pandino';
import loaderConfiguration from '@pandino/loader-configuration-dom';
import { PandinoProvider } from '@pandino/react-hooks';
import { CUSTOM_COMPONENT_INTERFACE_KEY, CustomComponent } from '@react-systemjs/component-api';
import { App } from './App';
import { SOME_SERVICE_INTERFACE_KEY, SomeServiceImpl } from './service';

const root = createRoot(document.querySelector('#root')!);

Expand All @@ -13,35 +16,50 @@ const pandino = new Pandino({
await pandino.init();
await pandino.start();

// export const ComponentOne: CustomComponent = (props) => {
// const [data, setData] = useState<{ firstName: string; lastName?: string }>({ ...props });
//
// return (
// <div className="component-one" style={{ border: '1px solid black', padding: '1rem' }}>
// <h3>Component One</h3>
// <p>FirstName: {data.firstName}</p>
// <p>LastName: {data.lastName}</p>
// </div>
// );
// };
//
// const reg = pandino.getBundleContext().registerService<CustomComponent>(CUSTOM_COMPONENT_INTERFACE_KEY, ComponentOne);
// window.setTimeout(() => {
// reg.unregister();
// }, 2000);

(async () => {
const componentOneBundle = await pandino.getBundleContext().installBundle('./component-one.system-manifest.json');
window.setTimeout(() => {
pandino.uninstallBundle(componentOneBundle as any);
const bundleContext = pandino.getBundleContext();

export const ComponentOne: CustomComponent = (props) => {
const [data, setData] = useState<{ firstName: string; lastName?: string }>({ ...props });

return (
<div className="component-one" style={{ border: '1px solid black', padding: '1rem' }}>
<h3>Component One</h3>
<p>FirstName: {data.firstName}</p>
<p>LastName: {data.lastName}</p>
</div>
);
};

const reg = bundleContext.registerService<CustomComponent>(CUSTOM_COMPONENT_INTERFACE_KEY, ComponentOne);
window.setTimeout(() => {
reg.unregister();
window.setTimeout(() => {
pandino.getBundleContext().installBundle('./component-one.system-manifest.json');
bundleContext.registerService<CustomComponent>(CUSTOM_COMPONENT_INTERFACE_KEY, ComponentOne);
}, 2000);
}, 2000);
})();
}, 2000);

// (async () => {
// const componentOneBundle = await bundleContext.installBundle('./component-one.system-manifest.json');
// window.setTimeout(() => {
// pandino.uninstallBundle(componentOneBundle as any);
// window.setTimeout(() => {
// bundleContext.installBundle('./component-one.system-manifest.json');
// }, 2000);
// }, 2000);
// })();

// (async () => {
// let someReg = await bundleContext.registerService(SOME_SERVICE_INTERFACE_KEY, new SomeServiceImpl());
// window.setTimeout(async () => {
// someReg.unregister();
// window.setTimeout(async () => {
// someReg = await bundleContext.registerService(SOME_SERVICE_INTERFACE_KEY, new SomeServiceImpl());
// }, 2000);
// }, 2000);
// })();

root.render(
<PandinoProvider ctx={pandino.getBundleContext()}>
<PandinoProvider ctx={bundleContext}>
<App />
</PandinoProvider>,
);
11 changes: 11 additions & 0 deletions examples/react-systemjs/packages/app/src/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const SOME_SERVICE_INTERFACE_KEY = 'SomeService';

export interface SomeService {
someMethod(): string;
}

export class SomeServiceImpl implements SomeService {
someMethod(): string {
return "some fine service";
}
}
2 changes: 1 addition & 1 deletion packages/@pandino/pandino-api/src/service/service-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ServiceReference } from '../../dist/esm/dist';
import { ServiceReference } from './service-reference';

export interface ServiceUtils {
getBestServiceReference(refs: ServiceReference<any>[]): ServiceReference<any> | undefined;
Expand Down
33 changes: 20 additions & 13 deletions packages/@pandino/react-hooks/src/useTrackService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useBundleContext } from './PandinoContext';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import type { ServiceEvent, ServiceListener, ServiceUtils } from '@pandino/pandino-api';
import { FRAMEWORK_SERVICE_UTILS } from '@pandino/pandino-api';

Expand All @@ -10,11 +10,12 @@ export interface SimpleTracker<T> {
export type ServiceTrackerHook = <T>(filter: string) => SimpleTracker<T>;

export const useTrackService: ServiceTrackerHook = <T>(filter: string) => {
const isInitialMount = useRef(true);
const { bundleContext } = useBundleContext();
const serviceUtilsRef = bundleContext.getServiceReference<ServiceUtils>(FRAMEWORK_SERVICE_UTILS);
const serviceUtils = bundleContext.getService(serviceUtilsRef);
const getService = useCallback<(filter: string) => T | undefined>(
(filter: string) => {
const serviceUtilsRef = bundleContext.getServiceReference<ServiceUtils>(FRAMEWORK_SERVICE_UTILS);
const serviceUtils = bundleContext.getService(serviceUtilsRef);
const refs = bundleContext.getServiceReferences(undefined, filter);
const ref = serviceUtils.getBestServiceReference(refs);
if (ref) {
Expand All @@ -41,22 +42,28 @@ export const useTrackService: ServiceTrackerHook = <T>(filter: string) => {
},
};
}, [filter]);
const [listener, setListener] = useState<ServiceListener | undefined>();
const [listener, setListener] = useState<ServiceListener | undefined>(createListener());

useEffect(() => {
if (listener) {
bundleContext.removeServiceListener(listener);
}
if (isInitialMount.current) {
isInitialMount.current = false;
bundleContext.addServiceListener(listener, filter);
} else {
// only run this branch on updates, no at initial renders
setListener((prevListener) => {
bundleContext.removeServiceListener(prevListener);

const newListener = createListener();
const newListener = createListener();

setListener(newListener);
bundleContext.addServiceListener(newListener, filter);

bundleContext.addServiceListener(newListener, filter);
return newListener;
});

setTracker({
service: getService(filter),
});
setTracker({
service: getService(filter),
});
}
}, [filter]);

return tracker;
Expand Down

0 comments on commit a722ed9

Please sign in to comment.