Use react-router v4
authorFrank Bessou <frank.bessou@logilab.fr>
Mon, 24 Apr 2017 12:18:10 +0200
changeset 131 8b57e0779065
parent 130 e0bb0419cbc4
child 132 2b6ce8199a46
Use react-router v4
package.json
src/components/BaseViews.js
src/components/Entities.js
src/components/Entity.js
src/components/NavLink.js
src/components/Root.js
src/components/Workflow.js
src/index.js
test/index.js
--- a/package.json	Mon Apr 24 10:54:15 2017 +0200
+++ b/package.json	Mon Apr 24 12:18:10 2017 +0200
@@ -12,7 +12,6 @@
     "react": "^15",
     "react-dom": "^15",
     "react-jsonschema-form": "^0.40.0",
-    "react-router": "^2.7.0",
     "whatwg-fetch": "^0.11.0"
   },
   "devDependencies": {
@@ -25,7 +24,7 @@
     "chai": "^3.5.0",
     "chai-as-promised": "^6.0.0",
     "diff": "^3.2.0",
-    "enzyme": "^2.4.1",
+    "enzyme": "^2.8.2",
     "eslint": "^3.11.1",
     "eslint-plugin-import": "^2.2.0",
     "eslint-plugin-react": "^6.7.1",
@@ -44,6 +43,9 @@
     "mocha": "^2.5.3",
     "react-addons-test-utils": "^15.3.2",
     "react-dom": "^15.5.4",
+    "react-router": "^4.1.1",
+    "react-router-dom": "^4.1.1",
+    "react-router-test-context": "^0.1.0",
     "react-test-renderer": "^15.5.4",
     "script-loader": "^0.7.0",
     "sinon": "^2.1.0",
--- a/src/components/BaseViews.js	Mon Apr 24 10:54:15 2017 +0200
+++ b/src/components/BaseViews.js	Mon Apr 24 12:18:10 2017 +0200
@@ -1,5 +1,5 @@
 import React from 'react';
-import {Link} from 'react-router';
+import {Link} from 'react-router-dom';
 import {isEmpty} from 'lodash/lang';
 import {PropTypeAction, PropTypesEntitiesModel, PropTypesResourceModel} from '../model';
 import {PropTypeJsonaryWrapper} from '../jsonaryutils';
--- a/src/components/Entities.js	Mon Apr 24 10:54:15 2017 +0200
+++ b/src/components/Entities.js	Mon Apr 24 12:18:10 2017 +0200
@@ -12,7 +12,7 @@
     }
 
     componentDidMount() {
-        const {etype} = this.props.params;
+        const {etype} = this.props.match.params;
         Api.getEntities(etype)
             .then(entities => this.setState({entities: entities}));
     }
@@ -26,8 +26,10 @@
 }
 
 Entities.propTypes = {
-    params: React.PropTypes.shape({
-        etype: React.PropTypes.string.isRequired,
+    match: React.PropTypes.shape({
+        params: React.PropTypes.shape({
+            etype: React.PropTypes.string.isRequired,
+        }),
     }),
 };
 
--- a/src/components/Entity.js	Mon Apr 24 10:54:15 2017 +0200
+++ b/src/components/Entity.js	Mon Apr 24 12:18:10 2017 +0200
@@ -1,6 +1,6 @@
 import React from 'react';
 import {FormWrapper} from './Form';
-import {Link} from 'react-router';
+import {Link} from 'react-router-dom';
 
 import {isEmpty} from 'lodash/lang';
 import {merge} from 'lodash/object';
@@ -180,8 +180,8 @@
 
     constructor(props) {
         super(props);
-        this.etype = props.params.etype;
-        this.eid = props.params.eid;
+        this.etype = props.match.params.etype;
+        this.eid = props.match.params.eid;
         this.state = {entity: null};
         this.getEditionSchema = this.getEditionSchema.bind(this);
         this.updateEntity = this.updateEntity.bind(this);
@@ -216,16 +216,16 @@
     }
 
     navigateToSelf() {
-        this.context.router.push(this.state.entity.data.getLink('self').href);
+        this.context.router.history.push(this.state.entity.data.getLink('self').href);
     }
 
     navigateToParent() {
         const parentRoute = this.state.entity.data.getLink('up').href;
         if (parentRoute) {
-            this.context.router.push(parentRoute);
+            this.context.router.history.push(parentRoute);
         } else {
             // XXX Use previous location as fallback
-            this.context.router.push('/');
+            this.context.router.history.push('/');
         }
     }
 
@@ -233,7 +233,7 @@
         if (this.state.entity === null) {
             return <div>loading...</div>;
         }
-        if (this.props.params.view === 'edit') {
+        if (this.props.match.params.view === 'edit') {
             const redirectPath = `/${this.etype}/${this.eid}`;
             return (
                 <EntityEditForm {...this.props}
@@ -243,7 +243,7 @@
                     redirectPath={ redirectPath }
                 />
             );
-        } else if (this.props.params.view === 'delete') {
+        } else if (this.props.match.params.view === 'delete') {
             return (
                 <EntityDeletionView
                     entity={this.state.entity}
@@ -258,10 +258,12 @@
 }
 
 Entity.propTypes = {
-    params: React.PropTypes.shape({
-        etype: React.PropTypes.string.isRequired,
-        eid: React.PropTypes.string.isRequired,
-        view: React.PropTypes.string,
+    match: React.PropTypes.shape({
+        params: React.PropTypes.shape({
+            etype: React.PropTypes.string.isRequired,
+            eid: React.PropTypes.string.isRequired,
+            view: React.PropTypes.string,
+        }),
     }),
 };
 
@@ -390,11 +392,11 @@
 
     constructor(props, context) {
         super(props, context);
-        this.etype = props.params.etype;
+        this.etype = props.match.params.etype;
     }
 
     componentDidMount() {
-        const {etype} = this.props.params;
+        const {etype} = this.props.match.params;
         HypermediaClient.getSchema(appendPath(`/${etype}`, '/schema?role=creation'))
             .then(schema => this.setState({schema: schema}));
     }
@@ -408,15 +410,17 @@
                     return;
                 }
                 const path = `/${this.etype}`;
-                this.context.router.push(path);
+                this.context.router.history.push(path);
             });
     }
 
 }
 
 EntityCreationForm.propTypes = {
-    params: React.PropTypes.shape({
-        etype: React.PropTypes.string,
+    match: React.PropTypes.shape({
+        params: React.PropTypes.shape({
+            etype: React.PropTypes.string,
+        }),
     }),
 };
 
@@ -424,9 +428,9 @@
 
     constructor(props, context) {
         super(props, context);
-        this.etype = props.params.etype;
-        this.rtype = props.params.rtype;
-        this.targetEid = props.params.eid;
+        this.etype = props.match.params.etype;
+        this.rtype = props.match.params.rtype;
+        this.targetEid = props.match.params.eid;
     }
 
     componentDidMount() {
@@ -437,17 +441,19 @@
     onSubmit({formData}) {
         const path = `/${this.etype}/${this.targetEid}`
         Api.relateEntity(this.etype, this.targetEid, this.rtype, formData)
-            .then(this.context.router.push(path));
+            .then(this.context.router.history.push(path));
     }
 
 }
 
 AddRelated.propTypes = {
-    params: React.PropTypes.shape({
-        etype: React.PropTypes.string.isRequired,
-        rtype: React.PropTypes.string.isRequired,
-        eid: React.PropTypes.number.isRequired,
-    }).isRequired,
+    match: React.PropTypes.shape({
+        params: React.PropTypes.shape({
+            etype: React.PropTypes.string.isRequired,
+            rtype: React.PropTypes.string.isRequired,
+            eid: React.PropTypes.number.isRequired,
+        }).isRequired,
+    }),
 };
 
 export class EntityEditForm extends EntityForm {
@@ -469,7 +475,7 @@
         Api.updateEntity(etype, eid, formData)
             .then(entity => {
                 this.props.updateEntity(entity);
-                this.context.router.push(this.props.redirectPath);
+                this.context.router.history.push(this.props.redirectPath);
             });
     }
 
--- a/src/components/NavLink.js	Mon Apr 24 10:54:15 2017 +0200
+++ b/src/components/NavLink.js	Mon Apr 24 12:18:10 2017 +0200
@@ -1,5 +1,5 @@
 import React from 'react';
-import {Link} from 'react-router';
+import {NavLink as Link} from 'react-router-dom';
 
 export default function NavLink(props) {
     return <Link {...props} activeClassName="active" />;
--- a/src/components/Root.js	Mon Apr 24 10:54:15 2017 +0200
+++ b/src/components/Root.js	Mon Apr 24 12:18:10 2017 +0200
@@ -1,5 +1,5 @@
 import React from 'react';
-import {Link} from 'react-router';
+import {Link} from 'react-router-dom';
 import HypermediaClient from '../services/hypermedia';
 
 export class Root extends React.Component {
--- a/src/components/Workflow.js	Mon Apr 24 10:54:15 2017 +0200
+++ b/src/components/Workflow.js	Mon Apr 24 12:18:10 2017 +0200
@@ -52,7 +52,7 @@
                             possibleTransitions: possibleTransitions,
                             workflowState: trinfo.to_state,
                         });
-                        this.context.router.push(path);
+                        this.context.router.history.push(path);
                     });
             });
     }
--- a/src/index.js	Mon Apr 24 10:54:15 2017 +0200
+++ b/src/index.js	Mon Apr 24 12:18:10 2017 +0200
@@ -1,6 +1,6 @@
 import React from 'react';
 import {render} from 'react-dom';
-import {Router, Route, browserHistory} from 'react-router';
+import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';
 
 import {App, NotFound} from './components/App';
 import {Root} from './components/Root';
@@ -12,17 +12,17 @@
 const appElement= document.getElementById('app');
 
 render((
-    <Router history={browserHistory}>
-        <Route path='' component={App}>
-            <Route exact path='/' component={Root} />
-            <Route path='/'>
-                <Route path=":etype" component={Entities} />
-                <Route path=":etype/new" component={EntityCreationForm} />
-                <Route path=":etype/:eid(/:view)" component={Entity} />
-                <Route path=":etype/:eid/relationships/:rtype" component={AddRelated} />
-                <Route path=":etype/:eid/relationships/:rtype" component={AddRelated} />
+    <Router>
+        <App>
+            <Switch>
+                <Route exact path='/' component={Root} />
+                <Route path="/:etype/:eid/relationships/:rtype" component={AddRelated} />
+                <Route path="/:etype/new" component={EntityCreationForm} />
+                <Route path="/:etype/:eid/:view" component={Entity} />
+                <Route path="/:etype/:eid/" component={Entity} />
+                <Route path="/:etype" component={Entities} />
                 <Route path="*" component={NotFound} />
-            </Route>
-        </Route>
+            </Switch>
+        </App>
     </Router>
 ), appElement);
--- a/test/index.js	Mon Apr 24 10:54:15 2017 +0200
+++ b/test/index.js	Mon Apr 24 12:18:10 2017 +0200
@@ -3,10 +3,11 @@
 
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
-import {shallow} from 'enzyme';
+import {shallow, render} from 'enzyme';
 import sinon from 'sinon';
 import {assert} from 'sinon';
 import React from 'react';
+import createRouterContext from 'react-router-test-context'
 import {merge} from 'lodash/object';
 
 import {buildFormData, appendPath} from '../src/utils';
@@ -127,13 +128,19 @@
 });
 
 describe('<CollectionItemLink />', () => {
+    const renderOptions = {
+        context: createRouterContext(),
+        childContextTypes: {router: React.PropTypes.object},
+    };
+
     it('renders a link with title and proper URL', () => {
         const item = {title: 'bob', id:'123'};
         const itemSchema = {links: [{rel: 'item', href: '/user/{id}'}]};
         const wrappedItem = wrapEntityData(item, itemSchema);
-        const wrapper = shallow(<CollectionItemLink entity={wrappedItem}/>);
+        const component = <CollectionItemLink entity={wrappedItem}/>;
 
-        const link = wrapper.render();
+        const wrapper = shallow(component);
+        const link = render(component, renderOptions);
 
         expect(wrapper.is('Link')).to.be.true;
         expect(wrapper.children()).to.have.length(1);