@@ -144,11 +144,90 @@ const useMockOption = <T extends keyof MockOptions>(key: T): MockOptions[T] => {
144144 return useContext ( MockOptionsContext ) [ key ] ;
145145} ;
146146
147+ function useHadronPluginActivate < T , S extends Record < string , ( ) => unknown > > (
148+ config : HadronPluginConfig < T , S > ,
149+ services : S | undefined ,
150+ props : T
151+ ) {
152+ const registryName = `${ config . name } .Plugin` ;
153+ const isMockedEnvironment = useMockOption ( 'mockedEnvironment' ) ;
154+ const mockServices = useMockOption ( 'mockServices' ) ;
155+
156+ const globalAppRegistry = useGlobalAppRegistry ( ) ;
157+ const localAppRegistry = useLocalAppRegistry ( ) ;
158+
159+ const serviceImpls = Object . fromEntries (
160+ Object . keys ( {
161+ ...( isMockedEnvironment ? mockServices : { } ) ,
162+ ...services ,
163+ } ) . map ( ( key ) => {
164+ try {
165+ return [
166+ key ,
167+ isMockedEnvironment && mockServices ?. [ key ]
168+ ? mockServices [ key ]
169+ : services ?. [ key ] ( ) ,
170+ ] ;
171+ } catch ( err ) {
172+ if (
173+ err &&
174+ typeof err === 'object' &&
175+ 'message' in err &&
176+ typeof err . message === 'string'
177+ )
178+ err . message += ` [locating service '${ key } ' for '${ registryName } ']` ;
179+ throw err ;
180+ }
181+ } )
182+ ) as Services < S > ;
183+
184+ const [ { store, actions } ] = useState (
185+ ( ) =>
186+ localAppRegistry . getPlugin ( registryName ) ??
187+ ( ( ) => {
188+ const plugin = config . activate (
189+ props ,
190+ {
191+ globalAppRegistry,
192+ localAppRegistry,
193+ ...serviceImpls ,
194+ } ,
195+ createActivateHelpers ( )
196+ ) ;
197+ localAppRegistry . registerPlugin ( registryName , plugin ) ;
198+ return plugin ;
199+ } ) ( )
200+ ) ;
201+
202+ return { store, actions } ;
203+ }
204+
147205export type HadronPluginComponent <
148206 T ,
149207 S extends Record < string , ( ) => unknown >
150208> = React . FunctionComponent < T > & {
151209 displayName : string ;
210+
211+ /**
212+ * Hook that will activate plugin in the current rendering scope without
213+ * actually rendering it. Useful to set up plugins that are rendered
214+ * conditionally and have to subscribe to event listeners earlier than the
215+ * first render in their lifecycle
216+ *
217+ * @example
218+ * const Plugin = registerHadronPlugin(...);
219+ *
220+ * function Component() {
221+ * Plugin.useActivate();
222+ * const [pluginVisible] = useState(false);
223+ *
224+ * // This Plugin component will already have its store set up and listening
225+ * // to the events even before rendering
226+ * return (pluginVisible && <Plugin />)
227+ * }
228+ */
229+ useActivate ( props : T ) : void ;
230+
152231 /**
153232 * Convenience method for testing: allows to override services and app
154233 * registries available in the plugin context
@@ -228,15 +307,10 @@ export function registerHadronPlugin<
228307 S extends Record < string , ( ) => unknown >
229308> ( config : HadronPluginConfig < T , S > , services ?: S ) : HadronPluginComponent < T , S > {
230309 const Component = config . component ;
231- const registryName = `${ config . name } .Plugin` ;
232310 const Plugin = ( props : React . PropsWithChildren < T > ) => {
233311 const isMockedEnvironment = useMockOption ( 'mockedEnvironment' ) ;
234312 const mockedPluginName = useMockOption ( 'pluginName' ) ;
235313 const disableRendering = useMockOption ( 'disableChildPluginRendering' ) ;
236- const mockServices = useMockOption ( 'mockServices' ) ;
237-
238- const globalAppRegistry = useGlobalAppRegistry ( ) ;
239- const localAppRegistry = useLocalAppRegistry ( ) ;
240314
241315 // This can only be true in test environment when parent plugin is setup
242316 // with mock services. We allow parent to render, but any other plugin
@@ -250,52 +324,11 @@ export function registerHadronPlugin<
250324 return null ;
251325 }
252326
253- const serviceImpls = Object . fromEntries (
254- Object . keys ( {
255- ...( isMockedEnvironment ? mockServices : { } ) ,
256- ...services ,
257- } ) . map ( ( key ) => {
258- try {
259- return [
260- key ,
261- isMockedEnvironment && mockServices ?. [ key ]
262- ? mockServices [ key ]
263- : services ?. [ key ] ( ) ,
264- ] ;
265- } catch ( err ) {
266- if (
267- err &&
268- typeof err === 'object' &&
269- 'message' in err &&
270- typeof err . message === 'string'
271- )
272- err . message += ` [locating service '${ key } ' for '${ registryName } ']` ;
273- throw err ;
274- }
275- } )
276- ) as Services < S > ;
277-
278327 // We don't actually use this hook conditionally, even though eslint rule
279328 // thinks so: values returned by `useMock*` hooks are constant in React
280329 // runtime
281330 // eslint-disable-next-line react-hooks/rules-of-hooks
282- const [ { store, actions } ] = useState (
283- ( ) =>
284- localAppRegistry . getPlugin ( registryName ) ??
285- ( ( ) => {
286- const plugin = config . activate (
287- props ,
288- {
289- globalAppRegistry,
290- localAppRegistry,
291- ...serviceImpls ,
292- } ,
293- createActivateHelpers ( )
294- ) ;
295- localAppRegistry . registerPlugin ( registryName , plugin ) ;
296- return plugin ;
297- } ) ( )
298- ) ;
331+ const { store, actions } = useHadronPluginActivate ( config , services , props ) ;
299332
300333 if ( isReduxStore ( store ) ) {
301334 return (
@@ -313,6 +346,9 @@ export function registerHadronPlugin<
313346 } ;
314347 return Object . assign ( Plugin , {
315348 displayName : config . name ,
349+ useActivate : ( props : T ) => {
350+ useHadronPluginActivate ( config , services , props ) ;
351+ } ,
316352 withMockServices (
317353 mocks : Partial < Registries & Services < S > > ,
318354 options ?: Partial < Pick < MockOptions , 'disableChildPluginRendering' > >
0 commit comments