Feature Based Routes
Once your application grows to more than a few pages, the need for a routing solution becomes critical. Which one do you use? There are so many!
True to feature-u's ideology, no one routing solution is mandated. You are free to pick and choose whichever of the many solutions meet your needs.
With the advent of feature-u's V1 approach to
Cross Feature Communication
, it is now possible to integrate to a number
of the routing solutions available today out-of-the-box!
By using a pull
philosophy in accumulating
resources over your features, you can easily gather a collection of
links for your menus and the route components themselves. This
solution employs Resource Contracts
and
Wildcard Processing
through the
Fassets.get()
and withFassets()
access points.
Using this technique, you can accumulate your routing resources
autonomously, allowing your app to grow feature by feature, not
needing to know everything in advance. All that is needed is to adopt
a naming convention (publicly promoted through the
fassets aspect
use
directive - a
Resource Contract
) for your links and route
components.
SideBar: While your routing solution may involve a feature-u
plugin (Extendable aspects
), for the most part
feature-u's Cross Feature Communication
should go a long way in
support of router integration out-of-the-box.
This section will briefly look at how feature-u projects can
integrate with two different routing solutions (the "tried and true"
react-router
, and a "new approach" based
on state: feature-router
).
react-router
react-router
V4 makes it very simple to perform
routing. By injecting a <Router>
component at the root of your
application, your <Route>
components can can live anywhere in the
tree. In turn, <Link>
components make it easy to navigate to the
various routes.
In this example, we employ an app
feature that has two
responsibilities:
- promote our top-level
<App>
component, and - manage our application routes.
Let's walk through this implementation, step by step ...
Step One (basic <App>
injection):
The first goal of our app
feature is to promote the top-level
<App>
component. This is easily accomplished by using
feature-u's Application Life Cycle Hooks
.
Here are the code snippets that implement this first goal.
features/app/featureName.js see: Best Practices Feature Name
/**
* Expose our featureName through a mini-meta module that is
* "importable" in all use-cases (a single-source-of-truth).
*/
export default 'app';
features/app/comp/App.js our top-level <App>
component
import React from 'react';
/**
* Our top-level App component (just a start).
*/
export default App = () => <div>Hello World</div>;
Now we simply promote the <App>
component into the DOM using our
Feature.appWillStart()
Application Life Cycle Hook
.
features/app/index.js our app
Feature, promoting the <App>
component
import React from 'react';
import {createFeature} from 'feature-u';
import App from './comp/App;
import featureName from './featureName';
export default createFeature({
name: featureName,
appWillStart({fassets, curRootAppElm}) {
// insure we don't clobber any supplied content
if (curRootAppElm) {
const msg = "***ERROR*** <App> 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 <App> through the conflicting artifact.";
console.log(`${msg} ... conflicting artifact:`, curRootAppElm);
throw new Error(msg);
}
return <App/>;
}
});
Step Two (adding links and routes):
The second goal of our app
feature is to manage our application
links and routes.
Our app
feature accomplishes this by introducing a
Resource Contract
, where we pull in links and
routes under the following naming convention:
*.link.comp
- any fasset resource suffixed with.link.comp
is expected to be a<Link>
component, and will be displayed in the App header menu.*.route.comp
- any fasset resource suffixed with.route.comp
is expected to be a<Route>
component, which will be rendered when the application navigates to a matching route.
Here is our app
feature with it's newly enhanced usage contract (see
use
directive):
features/app/index.js our app
Feature, enhanced with the new usage contract
import React from 'react';
import {createFeature} from 'feature-u';
import App from './comp/App;
import featureName from './featureName';
export default createFeature({
name: featureName,
fassets: { // NEW:
use: [ // our usage contract
'*.link.comp', // ... link components
'*.route.comp' // ... route components
]
},
appWillStart({fassets, curRootAppElm}) {
// insure we don't clobber any supplied content
if (curRootAppElm) {
const msg = "***ERROR*** <App> 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 <App> through the conflicting artifact.";
console.log(`${msg} ... conflicting artifact:`, curRootAppElm);
throw new Error(msg);
}
return <App/>;
}
});
And here is the fulfillment of our contract ... the enhanced <App>
component (with links and routes).
features/app/comp/App.js our top-level <App>
component with links and routes
import React from 'react';
import {BrowserRouter} from 'react-router-dom';
/**
* Our top-level App component (with links and routes)!
*/
const App = ({linkComps, routeComps}) => (
<BrowserRouter>
<div className="app">
<header>
<h1>My App</h1>
{ linkComps.map( (LinkComp, indx) => <LinkComp key={indx}/>) }
</header>
<main>
{ routeComps.map( (RouteComp, indx) => <RouteComp key={indx}/>) }
</main>
</div>
</BrowserRouter>
);
export default withFassets({
component: App,
mapFassetsToProps: {
linkComps: '*.link.comp',
routeComps: '*.route.comp'
}
});
Notice that in support of react-router
, we wrap everything
with <BrowserRouter>
.
Also notice that we inject the needed fasset resources using the
withFassets()
HoC.
Because our mapping uses wildcards, many resources will match
(accumulated into arrays). We simply inject the resources into our
component using array iteration.
SideBar: In regard to the react key using an array indices, please
see: React Keys (in array processing)
.
Suppliers (supplying content to the resource contract)
By employing wildcards, these links and routes can be supplied by any of our features - autonomously!!
The order in which these resources appear (when using wildcards) are feature expansion order (i.e. the same order that features are registered).
Here is a foo
feature that supplies it's own link and route
components.
features/foo/index.js a foo
feature supplying links and routes
import React from 'react';
import {createFeature} from 'feature-u';
import {Link, Route} from 'react-router-dom';
import featureName from './featureName'; // 'foo' ... a single-source-of-truth mini-meta module
const featureURLPath = `/${featureName}`; // the URL path is /foo
const link = () => <Link to={featureURLPath}>Foo</Link>;
const component = () => <Route path={featureURLPath} render={() => <div>Foo</div> }/>;
export default createFeature({
name: featureName,
fassets: {
defineUse: { // KEY: supply content under contract of the app feature
[`${featureName}.link.comp`]: link,
[`${featureName}.route.comp`]: component
}
}
});
Notice that we are using the defineUse
directive (even though a
define
would technically work). This indicates that we are
supplying this resource "under contract" (i.e. it should match a
corresponding use
directive). This allows feature-u to fail
fast if there is a problem (for example a misspelling).
Summing it up:
That's all there is to it. As we add features with links and route components, they are automatically picked up and rendered to our app!!
feature-u's wildcard support makes it easy to support most any
use case you might need. It is nice that we don't have to keep
modifying <App>
when we want to add a new menu link or another
route. Just by adhering to our naming convention they are
automatically pulled in and used.
feature-router
feature-router
(a feature-u plugin, packaged
separately) represents a new twist on routing.
Feature Routes are based on a very simple concept: allow the application state to drive the routes!
It operates through a series of feature-based routing functions that reason about the appState, and either return a rendered component, or null to allow downstream routes the same opportunity. Basically the first non-null return wins.
In feature based routing, you will not find the typical "route path to
component" mapping catalog, where (for example) some pseudo
route('signIn')
directive causes the SignIn screen to display
(which in turn causes the system to accommodate the request by
adjusting it's state).
In other words, you will never see application logic that re-routes to a signIn screen after checking to see if the user is authenticated. Rather, the appState is king! If the user is NOT authenticated (based on the route's analysis of the appState) the SignIn screen is automatically displayed ... Easy Peasy!
Depending on your perspective, this approach can be more robust and natural.
Want to see an example? Because feature-router
was
built as a feature-u plugin, you can find examples directly in
it's documentation.
Please Note that it is possible to use feature-router
in conjunction with other routing solutions!
SideBar: Another characteristic of this routing plugin is it
allows features to promote their own screens in an encapsulated and
autonomous way! As we have seen, this goal can also be accomplished
through feature-u's V1 approach to Cross Feature Communication
This discussion is obviously an introduction. There is more to this
plugin that we haven't touched on. Should you choose to use it,
please refer to the feature-router
docs.