Application Life Cycle Hooks

Because feature-u is in control of launching your application, it has a unique opportunity to introduce Application Life Cycle Hooks. These hooks allow your features to initialize themselves, performing app-specific initialization and even inject static content in the root of your DOM.

Three hooks are provided through the following built-in Feature aspects:

  1. Feature.appWillStart - invoked early in app startup (supports accumulative static root DOM injection)
  2. Feature.appInit - invoked later in app startup (supports blocking async initialization)
  3. Feature.appDidStart - invoked when app startup completes (triggers "app is running" processes)

App Initialization

Application Life Cycle Hooks greatly simplify your app's mainline startup process, because initialization specific to a given feature can be encapsulated in that feature.

appWillStart

The Feature appWillStart() life-cycle hook is invoked one time, very early in the app startup process. It supports both general app-specific initialization, as well as accumulative static root DOM injection.

API: appWillStart({fassets, curRootAppElm}): rootAppElm | void

Here you may perform any type of general initialization that is required by your feature. The following example initializes a PWA service worker:

appWillStart({fassets, curRootAppElm}) {
  serviceWorker.register();
}

Injecting DOM Content

In addition, the appWillStart() life-cycle hook can optionally inject static content in the app's DOM root. Any return is interpreted as the app's new rootAppElm (an accumulative process ... see below).

Here is an example that injects new root-level content:

appWillStart({fassets, curRootAppElm}) {
  ... other app-specific initialization here ...
  return (
    <Drawer ...>
      {curRootAppElm}
    </Drawer>
  );
}

Here is an example of injecting a new sibling to curRootAppElm (using React Fragments):

appWillStart: ({fassets, curRootAppElm}) => (
  <>
    <Notify/>
    {curRootAppElm}
  </>
)

IMPORTANT:

You may have noticed (in the examples above) that injecting DOM content (via the function return) is an accumulative process. Any new rootAppElm returned from this hook must include the supplied curRootAppElm parameter.

The curRootAppElm parameter (when non-null) represents content from other features (within your app) or aspects (used by your app). As a result, by including it in your injection, it accommodates the accumulative process of other feature/aspect injections!

This is outside the control of feature-u, and if you neglect to do it, you will be silently dropping content on the floor ... wondering why some feature/aspect is NOT working.

This constraint even extends to cases where the content you are injecting doesn't support children. In this case you need to throw an error, and emit applicable context in a log. Here is an example:

appWillStart({fassets, curRootAppElm}) {
  // MyContent does NOT support children
  // ... insure we don't clobber any supplied content
  if (curRootAppElm) {
    const msg = "***ERROR*** <MyContent> does NOT support children " +
                "but another feature/aspect is attempting to inject it's content. " +
                "Please resolve either by adjusting the feature expansion order, " +
                "or promoting <MyContent> through the conflicting artifact.";
    console.log(`${msg} ... conflicting artifact:`, curRootAppElm);
    throw new Error(msg);
  }
  return <MyContent .../>;
}
  • In many cases (such as a feature conflict), this can be resolved by adjusting the feature expansion order.

  • In other cases, it may seem as though you have hit an impasse. For example, if your content doesn't support children, and an aspect you are using doesn't support children. Normally this doesn't mean that you can't use your component, it merely means that you must promote your component in a different way ... most likely through the Aspect in conflict.

Because this check is rather tedious, feature-u provides a convenient assertNoRootAppElm() function that performs this check on your behalf. The following code snippet is equivalent:

appWillStart({fassets, curRootAppElm}) {
  assertNoRootAppElm(curRootAppElm, '<MyContent>'); // insure no content is clobbered (children NOT supported)
  return <MyContent .../>;
}

appInit

The Feature appInit() life-cycle hook is invoked one time, later in the app startup process. It supports blocking async initialization.

API: appInit({showStatus, fassets, [getState], [dispatch], [injectedAspectParams]}): promise | void

This hook is invoked when the app is nearly up-and-running.

  • The React Registration has already occurred (via the registerRootAppElm() callback). As a result, you can rely on utilities that require an app-specific rootAppElm to exist.

  • You have access to the getState() and dispatch() functions, assuming you are using redux (when detected by feature-u's plugable aspects).

    These parameters are actually injected by the feature-redux Aspect, and are examples of what can be injected by any Aspect (please refer your specific Aspect's documentation to determine other parameters).

Just like the appWillStart hook, you may perform any type of general initialization that is required by your feature.

However the hallmark of this hook is you can block for any asynchronous initialization to complete. By simply returning a promise, feature-u will wait for the process to complete.

The user is kept advised of any long-running async processes. By default an 'initializing feature: {feature.name}' message is used, but you can customize it through the supplied showStatus() function parameter.

The following example shows three processes managed by appInit() (one synchronous, and two asynchronous):

async appInit({showStatus, fassets, getState, dispatch}) {

  // default view is our TODO List
  showStatus('Defaulting view to TODO List);
  dispatch( fassets.actions.changeView('ToDoList') );

  // initialize our DB Connection
  showStatus('Initializing our DB Connection);
  await initDB();

  // maintain the current GPS device location in our appState
  showStatus('Initializing GPS Location');
  const location = await getCurPos();
  dispatch( setLocation(location) );
},

appDidStart

The Feature appDidStart() life-cycle hook is invoked one time, once the app startup process has completed. It can be used to trigger "the app is running" events.

API: appDidStart({fassets, [getState], [dispatch], [injectedAspectParams]}): void

Because the app is up-and-running at this time, you have access to the getState() and dispatch() functions, assuming you are using redux (when detected by feature-u's plugable aspects). These parameters are actually injected by the feature-redux Aspect, and are examples of what can be injected by any Aspect (please refer your specific Aspect's documentation to determine other parameters).

A typical usage for this hook is to "kick start" some early application logic. The following example starts the process of authenticating the user ... either an automatic signin (with saved credentials), or a manual signin (managing the signin screens):

appDidStart({fassets, getState, dispatch}) {
  dispatch( actions.authSignIn() );
}

results matching ""

    No results matching ""