A Pandino library providing handy tools for React-based applications.
This package is part of the pandino-root monorepo. For detailed information about what is Pandino / how this package fits into the ecosystem, please consult with the related documentation(s).
In order for hooks provided by this library to work, as a first step we must initialize the PandinoProvider
:
import { createRoot } from 'react-dom/client';
import Pandino from '@pandino/pandino';
import loaderConfiguration from '@pandino/loader-configuration-dom';
import { PandinoProvider } from '@pandino/react-hooks';
const root = createRoot(document.querySelector('#root')!);
const pandino = new Pandino({
...loaderConfiguration,
});
await pandino.init();
await pandino.start();
// FYI: PandinoProvider's `ctx` prop expects any BundleContext instance, it doesn't need to be the system bundle's context.
root.render(
<PandinoProvider ctx={pandino.getBundleContext()}>
<YourAppHere />
</PandinoProvider>,
);
Use this hook to obtain the BundleContext
reference registered with PandinoProvider
import type { FC } from 'react';
import { useBundleContext } from '@pandino/react-hooks';
export const MyComponent: FC = () => {
const { bundleContext } = useBundleContext();
console.log(bundleContext);
// ...
};
This hook is intended to be used in scenarios where we would like to intercept / decorate e.g. functions.
The intercepted reference is NOT "renewed" if post-registration a more fitting / stronger registration occurs.
This means that e.g. once a component is loaded, the intercepted references will remain the same!
Intercepting a function:
import { OBJECTCLASS } from '@pandino/pandino-api';
import { useServiceInterceptor } from '@pandino/react-hooks';
type Formatter = (input: string) => string;
export const ComponentOne: CustomComponent = (props) => {
const intercept = useServiceInterceptor();
const formatter = intercept<Formatter>(`(${OBJECTCLASS}=x-formatter)`, (input: string) => input.split('').reverse().join(''));
return (
<p>Decorator test: {formatter('abcd')}</p>
);
};
Defining an interceptor:
bundleContext.registerService<Formatter>('x-formatter', (input) => input.toUpperCase());
The intercept
function also takes a 3rd argument
This is a simple hook which expects a filter
parameter and returns a Service or undefined
.
When a service is removed from the system or if is not present to begin with, the hook will trigger the component and
the returned value will be undefined
.
Developers MUST handle the undefined scenarios explicitly.
Service changes trigger the tracker, which means related components will also be triggered!
import type { FC } from 'react';
import { OBJECTCLASS } from '@pandino/pandino-api';
import { useTrackService } from '@pandino/react-hooks';
export const MyComponent: FC = () => {
const { service: serviceImpl } = useTrackService<ServiceInterface>(`(${OBJECTCLASS}=${SERVICE_INTERFACE_KEY})`);
serviceImpl?.someMethod(); // Notice the usage of `?`
// ...
};
This hook works similarly to useTrackService
, the only difference is that it handles React components.
Component contract:
import type { FC } from 'react';
export const CUSTOM_COMPONENT_INTERFACE_KEY = '@some-scope/component-api/CustomComponent';
export interface CustomComponent extends FC<ComponentProps> {}
export interface ComponentProps {
firstName: string;
lastName?: string;
}
Component implementation:
import { useState } from 'react';
import type { CustomComponent } from '@some-scope/component-api';
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>
);
};
Component registration:
import type { BundleActivator, BundleContext, ServiceRegistration } from '@pandino/pandino-api';
import type { CustomComponent } from '@some-scope/component-api';
import { CUSTOM_COMPONENT_INTERFACE_KEY } from '@some-scope/component-api';
import { ComponentOne } from './ComponentOne';
export default class SomeActivator implements BundleActivator {
private reg?: ServiceRegistration<CustomComponent>;
async start(context: BundleContext) {
this.reg = context.registerService<CustomComponent>(CUSTOM_COMPONENT_INTERFACE_KEY, ComponentOne);
}
async stop(context: BundleContext) {
this.reg?.unregister();
}
}
Consumer app:
import type { FC } from 'react';
import { OBJECTCLASS } from '@pandino/pandino-api';
import { useTrackComponent } from '@pandino/react-hooks';
import type { CustomComponent } from '@some-scope/component-api';
import { CUSTOM_COMPONENT_INTERFACE_KEY } from '@some-scope/component-api';
export const MyComponent: FC = () => {
const ExternalComponent = useTrackComponent<CustomComponent>(`(${OBJECTCLASS}=${CUSTOM_COMPONENT_INTERFACE_KEY})`);
if (ExternalComponent) {
return <ExternalComponent {...someProps} />;
}
return <>fallback content</>;
};
The ComponentProxy
component can be used as a wrapper which has a filter
prop, and renders the corresponding
component if found. Otherwise it renders it's children.
In essence it is a shorthand / syntax sugar for
useTrackComponent
import type { FC } from 'react';
import { useState } from 'react';
import { OBJECTCLASS } from '@pandino/pandino-api';
import { ComponentProxy } from '@pandino/react-hooks';
import { CUSTOM_COMPONENT_INTERFACE_KEY } from '@some-scope/component-api';
export const App: FC = () => {
const [firstName] = useState<string>('John');
return (
<ComponentProxy filter={`(${OBJECTCLASS}=${CUSTOM_COMPONENT_INTERFACE_KEY})`} firstName={firstName}>
<div className={'fallback'}>fallback for: {firstName}</div>
</ComponentProxy>
);
};
All props are passed to the found component except filter
.
Prop matching between desired component props and children are not ensured.
This component (currently) cannot validate props!
Eclipse Public License - v 2.0