Added usePlugin hooks

Summary:
`usePlugin(pluginFactory)` returns the current plugin instance's api that was exposed by the plugin directory.

Passing `pluginFactory` is technically strictly not needed, but having the user pass it, we can make sure it is strongly typed

Reviewed By: nikoant

Differential Revision: D22286293

fbshipit-source-id: 4268b6849b8cd3d524103de7eadbd6c0a65c7a61
This commit is contained in:
Michel Weststrate
2020-07-01 08:58:40 -07:00
committed by Facebook GitHub Bot
parent 952e929699
commit 159c0deaf1
5 changed files with 44 additions and 8 deletions

View File

@@ -16,6 +16,7 @@ import {
SandyPluginDefinition,
FlipperClient,
TestUtils,
usePlugin,
} from 'flipper-plugin';
import {selectPlugin} from '../reducers/connections';
@@ -92,8 +93,17 @@ test('PluginContainer can render Sandy plugins', async () => {
function MySandyPlugin() {
renders++;
const sandyContext = useContext(SandyPluginContext);
expect(sandyContext).not.toBe(null);
const sandyApi = usePlugin(plugin);
expect(Object.keys(sandyApi)).toEqual([
'connectedStub',
'disconnectedStub',
]);
expect(() => {
// eslint-disable-next-line
usePlugin(function bla() {
return {};
});
}).toThrowError(/didn't match the type of the requested plugin/);
return <div>Hello from Sandy</div>;
}

View File

@@ -9,6 +9,7 @@
import * as React from 'react';
import {FlipperClient} from '../plugin/Plugin';
import {usePlugin} from '../plugin/PluginContext';
type Events = {
inc: {
@@ -67,6 +68,11 @@ export function plugin(client: FlipperClient<Events, Methods>) {
}
export function Component() {
// TODO T69105011 add test for usePlugin including type assertions
return <h1>Hi from test plugin</h1>;
const api = usePlugin(plugin);
// @ts-expect-error
api.bla;
// TODO N.b.: state updates won't be visible
return <h1>Hi from test plugin {api.state.count}</h1>;
}

View File

@@ -54,6 +54,7 @@ test('it can render a plugin', () => {
<div>
<h1>
Hi from test plugin
0
</h1>
</div>
</body>

View File

@@ -12,7 +12,7 @@ import * as TestUtilites from './test-utils/test-utils';
export {SandyPluginInstance, FlipperClient} from './plugin/Plugin';
export {SandyPluginDefinition} from './plugin/SandyPluginDefinition';
export {SandyPluginRenderer} from './plugin/PluginRenderer';
export {SandyPluginContext} from './plugin/PluginContext';
export {SandyPluginContext, usePlugin} from './plugin/PluginContext';
// It's not ideal that this exists in flipper-plugin sources directly,
// but is the least pain for plugin authors.

View File

@@ -7,9 +7,28 @@
* @format
*/
import {createContext} from 'react';
import {SandyPluginInstance} from './Plugin';
import {createContext, useContext} from 'react';
import {SandyPluginInstance, FlipperPluginFactory} from './Plugin';
export const SandyPluginContext = createContext<
SandyPluginInstance | undefined
>(undefined);
export function usePlugin<PluginFactory extends FlipperPluginFactory<any, any>>(
plugin: PluginFactory,
): ReturnType<PluginFactory> {
const pluginInstance = useContext(SandyPluginContext);
if (!pluginInstance) {
throw new Error('Plugin context not available');
}
// In principle we don't *need* the plugin, but having it passed it makes sure the
// return of this function is strongly typed, without the user needing to create it's own
// context.
// But since we pass it, let's make sure the user did request the proper context
if (pluginInstance.definition.module.plugin !== plugin) {
throw new Error(
`Plugin context (${pluginInstance.definition.module.plugin}) didn't match the type of the requested plugin (${plugin})`,
);
}
return pluginInstance.instanceApi;
}