Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ When an `<Application>` is unmounted, it will call the `destroy()` method on the

`extensions` is an array of extensions to be loaded. Adding and removing items from this array will automatically load/unload the extensions. The first time this is handled happens before the application is initialised. See Pixi.js's [`extensions`](https://pixijs.download/release/docs/extensions.html) documentation for more info on extensions.

###### `onDestroy`

If provided, the `onDestroy` callback will be called after the `<Application>` component is unmounted and the associated Pixi.js application is destroyed, allowing you to do any additional clean-up or lifecycle handling you may need.

###### `rendererDestroyOptions`

When an `<Application>` is unmounted, it will call the `destroy()` method on the Pixi.js application instance. Provide this prop to override the default `RendererDestroyOptions` (the first argument to `destroy()`). See Pixi.js's [`destroy documentation`](https://pixijs.download/release/docs/app.Application.html#destroy) for more info.
Expand Down
8 changes: 8 additions & 0 deletions src/components/Application.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {

Check warning on line 1 in src/components/Application.tsx

View workflow job for this annotation

GitHub Actions / Verify (Lint, test:lint, false)

Run autofix to sort these imports!
FiberProvider,
useContextBridge,
} from 'its-fine';
Expand Down Expand Up @@ -38,6 +38,7 @@
defaultTextStyle,
destroyOptions,
extensions,
onDestroy,
onInit,
rendererDestroyOptions,
resizeTo,
Expand Down Expand Up @@ -96,8 +97,13 @@
applicationRef.current = application;
updateResizeTo();
onInit?.(application);
}, [onInit]);

Check warning on line 100 in src/components/Application.tsx

View workflow job for this annotation

GitHub Actions / Verify (Lint, test:lint, false)

React Hook useCallback has a missing dependency: 'updateResizeTo'. Either include it or remove the dependency array

const handleDestroy = useCallback(() =>
{
onDestroy?.();
}, [onDestroy]);

useIsomorphicLayoutEffect(() =>
{
if (extensions)
Expand Down Expand Up @@ -142,6 +148,7 @@
{
root = createRoot(canvasElement, {
destroyOptions,
onDestroy: handleDestroy,
onInit: handleInit,
rendererDestroyOptions,
});
Expand All @@ -150,11 +157,12 @@
// @ts-expect-error The value of `children` is fine, but `PixiReactChildNode` doesn't strictly adhere to the `ReactNode` structure.
root.render((<Bridge>{children}</Bridge>), applicationProps);
}
}, [

Check warning on line 160 in src/components/Application.tsx

View workflow job for this annotation

GitHub Actions / Verify (Lint, test:lint, false)

React Hook useIsomorphicLayoutEffect has a missing dependency: 'Bridge'. Either include it or remove the dependency array
applicationProps,
children,
destroyOptions,
handleInit,
handleDestroy,
rendererDestroyOptions,
resizeTo,
]);
Expand All @@ -162,7 +170,7 @@
useIsomorphicLayoutEffect(() =>
{
updateResizeTo();
}, [resizeTo]);

Check warning on line 173 in src/components/Application.tsx

View workflow job for this annotation

GitHub Actions / Verify (Lint, test:lint, false)

React Hook useIsomorphicLayoutEffect has a missing dependency: 'updateResizeTo'. Either include it or remove the dependency array

useIsomorphicLayoutEffect(() =>
{
Expand Down
1 change: 1 addition & 0 deletions src/core/createRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function createRoot(
destroyOptions: options.destroyOptions,
isInitialised: false,
isInitialising: false,
onDestroy: options.onDestroy,
rendererDestroyOptions: options.rendererDestroyOptions,
}) as ApplicationState;

Expand Down
1 change: 1 addition & 0 deletions src/helpers/unmountRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function unmountRoot(root: Root)
root.applicationState.rendererDestroyOptions,
root.applicationState.destroyOptions
);
root.applicationState.onDestroy?.();
}

roots.delete(root.internalState.canvas!);
Expand Down
3 changes: 3 additions & 0 deletions src/typedefs/ApplicationProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export interface BaseApplicationProps
/** @description A unique key which allows React to manage this component across changes in parent state. */
key?: Key,

/** @description Callback to be fired when the application is destroyed. */
onDestroy?: () => void

/** @description Callback to be fired when the application finishes initializing. */
onInit?: (app: Application) => void

Expand Down
1 change: 1 addition & 0 deletions src/typedefs/ApplicationState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export interface ApplicationState
destroyOptions: DestroyOptions;
isInitialised: boolean;
isInitialising: boolean;
onDestroy?: () => void;
rendererDestroyOptions: RendererDestroyOptions;
}
3 changes: 3 additions & 0 deletions src/typedefs/CreateRootOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export interface CreateRootOptions
/** @description Options to be passed to the application's `destroy` method. */
destroyOptions?: DestroyOptions

/** @description Callback to be fired when the application is destroyed. */
onDestroy?: () => void

/** @description Callback to be fired when the application finishes initializing. */
onInit?: (app: Application) => void

Expand Down
59 changes: 59 additions & 0 deletions test/e2e/components/Application.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,65 @@ describe('Application', () =>
expect(destroySpy).toHaveBeenCalledWith(rendererDestroyOptions, undefined);
});

it('unmounts with onDestroy callback', async () =>
{
let testApp = null as any as PixiApplication;
let testAppIsInitialised = false;

const onDestroySpy = vi.fn();

const TestChildComponent = () =>
{
const {
app,
isInitialised,
} = useApplication();

useEffect(() =>
{
testApp = app;
testAppIsInitialised = isInitialised;

return () =>
{
testApp = app;
testAppIsInitialised = isInitialised;
};
}, [
app,
isInitialised,
]);

return null;
};

const TestComponent = () => (
<Application onDestroy={onDestroySpy}>
<TestChildComponent />
</Application>
);

expect(roots.size).toEqual(0);

const { unmount } = await act(() => render(<TestComponent />));

expect(roots.size).toEqual(1);

await expect.poll(() => testAppIsInitialised).toEqual(true);

// sanity check that onDestroy has not yet been called
expect(onDestroySpy).not.toHaveBeenCalled();

unmount();

expect(roots.size).toEqual(0);

await expect.poll(() => isAppMounted(testApp)).toBeFalsy();

expect(onDestroySpy).toHaveBeenCalledTimes(1);
expect(onDestroySpy).toHaveBeenCalledWith();
});

it('unmounts during init', async () =>
{
let testApp = null as any as PixiApplication;
Expand Down
13 changes: 13 additions & 0 deletions test/unit/core/createRoot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
describe,
expect,
it,
vi,
} from 'vitest';
import { createRoot } from '../../../src/core/createRoot';

Expand Down Expand Up @@ -61,4 +62,16 @@ describe('createRoot', () =>
expect(root.applicationState.rendererDestroyOptions).toEqual({ removeView: true });
});
});

it('creates a new root with onDestroy callback', () =>
{
const onDestroySpy = vi.fn();

const target = document.createElement('canvas');
const root = createRoot(target, {
onDestroy: onDestroySpy,
});

expect(root.applicationState.onDestroy).toBe(onDestroySpy);
});
});
Loading