feature-react-navigation
Version:
Feature based navigation using react-navigation
161 lines (138 loc) • 4.19 kB
JavaScript
import React from 'react';
import * as ReactIs from 'react-is';
import { createAspect, launchApp } from 'feature-u';
import has from 'lodash.has';
import verify from './util/verify';
const logf = launchApp.diag.logf.newLogger(
'- ***feature-react-navigation*** routeAspect: '
);
export default function createRouteAspect(config = {}) {
// validate parameters
const check = verify.prefix('createRouteAspect() parameter violation: ');
check(
typeof config === 'object',
`
config parameter must be an object
Allowed parameters are :
- name type string
- Navigator type ReactElement
`
);
const { name = 'route', Navigator } = config;
check(name, 'name is required');
check(typeof name === 'string', 'name must be a string');
// Other fields are tested in genesis function
return createAspect({
name,
genesis,
validateFeatureContent,
assembleFeatureContent,
initialRootAppElm,
config: {
Navigator,
},
});
}
/**
* Control aspect config
*/
function genesis() {
logf('genesis() validating required config.Navigator');
if (!this.config.Navigator)
return `the ${this.name} aspect requires Navigator to be provided`;
if (!ReactIs.isValidElementType(this.config.Navigator)) {
return `the ${this.name} aspect requires Navigator to be a valid React Element in createRouteAspect object parameter !`;
}
return null;
}
/**
* Validate routes configuration
*
* @param {Object} feature Feature configuration
*
* @private
*/
function validateFeatureContent(feature) {
if (has(feature, 'route')) {
if (
typeof feature.route !== 'object' ||
typeof feature.route.routes !== 'object'
) {
return 'If a feature expose some routing, routes must be exposed under feature.route.routes param';
} else {
for (const key in feature.route.routes) {
if (has(feature.route.routes, key)) {
const el = feature.route.routes[key];
if (!ReactIs.isValidElementType(el.screen)) {
return 'Features routes must have a valid React elements under screen property';
}
}
}
}
}
return null; // valid
}
/**
* Accumulate all routes from our features.
*
* @param {Fassets} fassets the Fassets object used in cross-feature-communication.
*
* @param {Feature[]} activeFeatures - The set of active (enabled)
* features that comprise this application.
*
* @private
*/
function assembleFeatureContent(fassets, activeFeatures) {
// accumulate all routes from our features
const r = {};
activeFeatures.reduce((accum, feature) => {
if (!has(feature[this.name], 'routes')) {
return accum;
}
const { routes: routeContent } = feature[this.name];
if (routeContent) {
Object.assign(r, routeContent);
}
return accum;
}, []);
// retain for later use
this.routes = r;
}
/**
* Inject our `<AppContainer>` in the `rootAppElm`.
*
* We use `initialRootAppElm()` because `<AppContainer>` does NOT
* support children (by design).
*
* @param {Fassets} fassets the Fassets object used in cross-feature-communication.
*
* @param {reactElm} curRootAppElm - the current react app element root.
*
* @return {reactElm} rootAppElm seeded with our `<AppContainer>`.
*
* @private
*/
function initialRootAppElm(fassets, curRootAppElm) {
// no-op if we have NO routes
if (
Object.keys(this.routes).length === 0 &&
this.routes.constructor === Object
) {
// NOTE: for this condition, the appropriate logf.force() is generated (above)
return curRootAppElm;
}
// insure we don't clober any supplied content
// ... by design, <AppContainer> doesn't support children
if (curRootAppElm) {
throw new Error(
'***ERROR*** Please register routeAspect (from feature-react-navigation) before other Aspects ' +
'that inject content in the rootAppElm ... <AppContainer> does NOT support children.'
);
}
// seed the rootAppElm with our StateRouter
logf(
`initialRootAppElm() introducing <AppContainer> component into rootAppElm`
);
const { Navigator } = this.config;
return <Navigator routes={this.routes} />;
}