router5 / react-router5

Helpers for using router5 with React [MOVED]
MIT License
83 stars 8 forks source link

Segment Mapping API #19

Closed secretfader closed 8 years ago

secretfader commented 8 years ago

I love Router5. But now that I'm using it on multiple projects, the manual segment mapping is beginning to annoy me. This is something I find myself doing repeatedly. Here's an example:

const components = {
    'inbox':   Inbox,
    'compose': Compose
};

function Main(props) {
    const { route } = props;
    const segment = route ? route.name.split('.')[0] : undefined;

    return createElement(components[segment] || NotFound);
}

export default connect(routeNodeSelector(''))(Main);

There must be a better API for this behavior. I wonder if we can improve on it, and remove the need to manually split route names and pass the mapped component to createElement. This is something that react-router got right, and I'd like to find a way to improve the react-router5 project.

troch commented 8 years ago

You can't really avoid it, it's like switch statements in a redux reducer. But there are different ways to go about it. https://github.com/router5/helpers contains a few helpers to avoiid doing .split('.')[0]. You can also create your own helpers to help you find a component in a map, etc...

You can do stuff like the following:

import { startsWithSegment } from 'router5.helpers';

function Main(props) {
    const { route } = props;

    if (!route) {
        return NotFound;
    }

    const startsWith = startsWithSegment(route.name);

    if (startsWith('users')) return <Users />;
    if (startsWith('home')) return <Home />;
    /* etc... */
}
secretfader commented 8 years ago

@troch That makes sense. What I like most about react-router is there's one, clearly obvious way forward. Router5's strength is in the flexibility, but I also wonder if there should be an obvious solution for newcomers.

I'm working on a boilerplate for all of my new projects that uses Router5... I wonder, would you be open to extracting some of that work into an official guide? It's just fresh on my mind, as I'm working through the implementation.

troch commented 8 years ago

@nicholaswyoung I would be totally open to that. I have been really busy recently (working a react / redux / router5 large scale app) and I haven't got the time to update docs and examples reflecting my own experience with it. So that could motivate me.

secretfader commented 8 years ago

@troch I used your approach in the commit above, but I'm sure there are other ways that I can improve on the code that's in the repo. I'm also planning a series of blog posts on why I believe Router5 is the better solution, so I want to get this right before spreading the word.

ghost commented 8 years ago

@nicholaswyoung Please mention, when You're gonna release blog posts about it.

secretfader commented 8 years ago

@pszek Will do, probably on this thread.

secretfader commented 8 years ago

@pszek @troch I'm getting close to the ideal routing system over here. https://github.com/nicholaswyoung/react-boilerplate

It still has some kinks to be worked out, but I'm currently resolving issues.

troch commented 8 years ago

Cool, that's pretty much what I do myself. I went back to using a router5 middleware for data, for two reasons: data requests are fired a call stack earlier and I can also block for universal pre-rendering.

import Router5 from 'router5';
import historyPlugin from 'router5-history';
import routes from './routes';
import { transitionPath } from 'router5';

export const getPageTitle = (routes) => (toState, fromState) => {
    const { toActivate } = transitionPath(toState, fromState);
    let pageTitle = 'XXXXX';

    toActivate.forEach(segment => {
        const routeSegment = routes.find(r => r.name === segment);

        if (routeSegment.pageTitle) {
            pageTitle = routeSegment.pageTitle + ' | XXXXX';
        }
    });

    return pageTitle;
};

const pageTitleMiddleware = (routes) => (router) =>
    (store, toState, fromState, done) => {
        document.title = getPageTitle(routes)(toState, fromState);
        done();
    };

const routeDataMiddleware = (routes, waitForData) => (router) =>
    (store, toState, fromState) => {
        const { toActivate } = transitionPath(toState, fromState);

        const dataPromises = toActivate
            .map(segment => {
                const routeSegment = routes.find(r => r.name === segment);

                if (routeSegment && routeSegment.onActivate) {
                    return routeSegment.onActivate(store)(toState.params);
                }
            })
            .filter(promise => promise !== undefined);

        return waitForData ? Promise.all(dataPromises) : true;
    };

const waitForData = __IS_BROWSER__;
const middlewares = [ routeDataMiddleware(routes, waitForData) ];
const defaultRoute = routes.find(route => route.default === true);

if (__IS_BROWSER__) {
    middlewares.push(pageTitleMiddleware(routes));
}

export default function createRouter() {
    const router = new Router5()
        .add(routes)
        .setOption('useHash', true)
        .setOption('defaultRoute', defaultRoute)
        .usePlugin(historyPlugin({ forceDeactivate: true }))
        .useMiddleware(...middlewares);

    return router;
}
ghost commented 8 years ago

@nicholaswyoung XO && AVA, like it even more!

secretfader commented 8 years ago

@troch That's probably where I'll end up, eventually.

@Pszek And Enzyme (for testing React components).

Of course, I'm obligated to say this is the best boilerplate yet (because it's mine). But I can say without reservation that the tools I've preconfigured are genuinely the best.

secretfader commented 8 years ago

Also: the onActivate middleware should probably be it's own module. If @troch doesn't mind, I'll extract that into a module of it's own.

troch commented 8 years ago

Oh yeah, I just put everything in a file, but please do make it a module. The main idea is that it's the easiest way to have an universal application.

secretfader commented 8 years ago

@troch Do you have a preferred name, so it matches up with the existing router5 naming conventions?

troch commented 8 years ago

No I don't :)