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:

  1. promote our top-level <App> component, and
  2. 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/>;
  }
});

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.

results matching ""

    No results matching ""