jaredatron/simple-react-router

Call redirectTo from within router

Closed this issue · 2 comments

I would like to create a route which creates a piece of global state and then redirects to the the URL of that new state, I think the code speaks clearer than my words though:

export default class RouteHandler extends Router {
  routes(map: MapFn) {
    let that = this;
    map('/', LoginPage);
    map('/projects', ProjectsPage);
    map('/project/new', () => {
      let project = new Project();
      store.dispatch(actions.addProject(project));
      that.redirectTo('/project/' + project.uuid);
    });
    map('/project/:uuid', ProjectPage);
  }
}

Though it seems like this/that is always null.

It is possible to emulate this behaviour by modifying the global location like location.href = '/project/' + project.uuid; though that seems suboptimal.

Am I missing something or would this be a feature request?

I see what you're wanting and right now the Router isn't built to handle this. Because the router is a React component; when the url changes it triggers a react tree re-render. This means when you hit /projects/new you have to render something. You could use componentDidMount in a component to immediately redirect once that component is mounted.

Something like this:

class CreateProject extends React.Component {
  static contextTypes = {
    redirectTo: PropTypes.func.isRequired,
  };
  componentDidMount(){
      // do whatever you want to here
      this.context.redirectTo('/some-new-place-to-go');
  }
  render(){
    return <span>redirecting…</span>
  }
}

The above component has access to redirectTo via React Context variables provided by the Router component.

I agree with you that this sort of thing should be supported but I think it would require a significant change to the way this simple router is setup in apps. That major change being the router (that listens for history.pushState changes and decides what component to render) would need to be pulled up out of the react tree. A router outside of the react tree that just tells either a high level component or whatever state manager your using, that the router has changed what component to render. This would allow you to have some redirect routes that don't trigger a react tree re-render on every history.pushState.

I hope that helps

Yes thank you a lot for your both quick and very helpful response. You're hitting on a thing that I find weird about the react/javascript world in contrast to the ClojureScript world - why are there so many meta components, components which are just there to do other things than being rendered to HTML. But that's besides the point.

Here's the solution I ended up which is directly based on your help:

import { Router, MapFn, ComponentGift } from 'simple-react-router';
import store from 'src/state/store';
import { actions } from 'src/state/actions';
import * as React from 'react';
// Pages                                                                                       
import LoginPage from './Login';
import ProjectsPage from './Projects';
import ProjectPage from './ProjectPage';
import Project from 'src/Project';

class NewProjectRedirect extends React.Component<ComponentGift> {
  componentWillMount() {
    let project = new Project();
    store.dispatch(actions.addProject(project));
    // Here we rewrite the current URL in the URL-bar of the browser                           
    // in order to not break the browsers built in back and forward                            
    // buttons. After that we trigger the router libraries state                               
    // machine again.                                                                          
    history.replaceState({}, '', '/project/' + project.uuid);
    this.props.router.redirectTo('/project/' + project.uuid);
  }
  render() {
    return React.createElement('div', null, 'Redirecting...');
  }
}

export default class RouteHandler extends Router {
  routes(map: MapFn) {
    map('/', LoginPage);
    map('/projects', ProjectsPage);
    map('/project/new', NewProjectRedirect);
    map('/project/:uuid', ProjectPage);
  }
}

Note that its written in TypeScript and not JavaScript.

Since I'm writing TypeScript I've had to write type declarations for your library, but perhaps that's something you'd like to incorporate in to your library? I can see if I can figure out where they belong in a project tree and create a PR if you want them.