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:
Feature.appWillStart
- invoked early in app startup (supports accumulative static root DOM injection)Feature.appInit
- invoked later in app startup (supports blocking async initialization)Feature.appDidStart
- invoked when app startup completes (triggers "app is running" processes)
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, [appState], [dispatch]}): promise | void
This hook is invoked when the app is nearly up-and-running.
The
React Registration
has already occurred (via theregisterRootAppElm()
callback). As a result, you can rely on utilities that require an app-specificrootAppElm
to exist.You have access to the
appState
anddispatch()
function, assuming you are usingredux
(when detected by feature-u's plugable aspects).
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, appState, 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, [appState], [dispatch]}): void
Because the app is up-and-running at this time, you have access to
the appState
and dispatch()
function ... assuming you are using
redux
(when detected by feature-u's plugable
aspects).
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, appState, dispatch}) {
dispatch( actions.authSignIn() );
}