Skip to content

Commit 7ad5ffa

Browse files
committed
feat(core): allow attaching action creators to routes, and more...
FEATURE: Allow attaching action creators to routes – turn URL params to Redux state automatically. FEATURE: Allow dynamic routes – can now route with an array of data as well as a URL string. FEATURE: Add getState helper function – no longer need to pass initial state from server to client. BREAKING CHANGE: structure of routes has changed from a function to an array. Before: ```js const routes = url => { const path = url.split( '?' )[ 0 ]; const query = qs.parse( qs.extract( url )); switch ( path ) { case '/about': return <About />; case '/hello': return <Hello name={ query.name } />; case '/': return <Home />; default: return <NotFound />; } }; ``` After: ```js const routes = [ [ 'about', <About /> ], [ 'hello', { name: updateName }, <Hello /> ], [ '/', <Home /> ], [ '*', <NotFound /> ], ]; ``` This now allows dynamic routes and attaching action creators to routes to handle params. BREAKING CHANGE: Link component now takes **to** prop, not **url** prop. Before: ``` <Link url="/">Home</Link> ``` After: ``` <Link to="/">Home</Link> ``` This now allows Link component to also take a *to* array: ``` <Link to={[ 'users', id, 'followers', { page: 2 }]}>Followers</Link> ``` BREAKING CHANGE: updateUrl action creator has been removed from the public API in favour of changePageTo. Before: ``` dispatch( updateUrl( '/' )) ``` After: ``` dispatch( changePageTo( '/' )) ``` This now allows us to also pass in a *to* array: ``` dispatch( changePageTo([ 'users', id, 'followers', { page: 2 }])) ``` BREAKING CHANGE: now requires routerMiddleware. Before: none After: ```js const middleware = applyMiddleware( routerMiddleware( routes, { isServer })); const store = createStore( reducer, state, middleware ); ``` This allows us to batch the action creators we have attached to our routes into one dispatch. BREAKING CHANGE: now requires routerReducer instead of Redux combineReducers. Before: ```js const reducer = combineReducers({ ...reducers, url: urlReducer }); ``` After: ```js const reducer = routerReducer( reducers ); ``` Notice how urlReducer is no longer needed. BREAKING CHANGE: now handles popstate by default. No need to add listener and dispatch updateUrl manually. Before: ```js window.addEventListener( 'popstate', () => { const { hash, pathname, search } = window.location; const url = pathname + search + hash; store.dispatch( updateUrl( url )); }); ``` After: none
1 parent 56e9c3e commit 7ad5ffa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1043
-341
lines changed

.eslintrc

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
{
2-
"extends": "spaced"
2+
"extends": "spaced",
3+
"rules": {
4+
"no-param-reassign": [ 2, { "props": false }],
5+
},
36
}

README.md

+2-63
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,5 @@
11
# Universal Redux Router
22

3-
A very simple router for [Redux](https://github.com/rackt/redux)
4-
and [React](https://github.com/facebook/react) that works on
5-
both server and client.
6-
7-
All credit goes to
8-
[this article on minimalist routing in Redux](https://gist.github.com/HenrikJoreteg/530c1da6a5e0ff9bd9ad),
9-
[React Router](https://github.com/rackt/react-router),
10-
[Redux Router](https://github.com/rackt/redux-router) and
11-
[Redux Tiny Router](https://github.com/Agamennon/redux-tiny-router).
12-
Pretty much every idea here is stolen from the above.
13-
143
## Installation
154

165
```
@@ -19,57 +8,8 @@ npm install universal-redux-router
198

209
## Basic example
2110

22-
```javascript
23-
import React from 'react';
24-
import { combineReducers, createStore } from 'redux';
25-
import { Provider } from 'react-redux';
26-
import { Router, updateUrl, urlReducer as url } from 'universal-redux-router';
27-
28-
const reducer = combineReducers({ url });
29-
30-
const routes = url => {
31-
switch ( url ) {
32-
case '/hello-world':
33-
return <h1>Hello world</h1>;
34-
default:
35-
return <h1>Not found</h1>;
36-
}
37-
}
38-
39-
const store = createStore( reducer );
40-
41-
store.dispatch( updateUrl( '/hello-world' ));
42-
43-
const app = (
44-
<Provider store={ store }>
45-
<Router routes={ routes } />
46-
</Provider>
47-
);
48-
```
49-
50-
For more complete examples please look in
51-
[the examples directory](./examples/).
52-
53-
## API
54-
55-
### Actions
56-
57-
#### `UPDATE_URL`
58-
59-
### Action creators
60-
61-
#### `updateUrl( '/hello-world' )`
62-
63-
An action creator for `UPDATE_URL`
64-
65-
### Components
66-
67-
#### `<Router routes={ routes } />`
68-
#### `<Link url="/hello-world">Hola</Link>`
69-
70-
### Reducers
71-
72-
#### `urlReducer( '/old-url', { type: UPDATE_URL, url: '/new-url' })`
11+
... will add documentation on updated API in the next few days.
12+
For now please check out the examples directory.
7313

7414
## License
7515

@@ -78,4 +18,3 @@ An action creator for `UPDATE_URL`
7818
---
7919

8020
![Test status](https://img.shields.io/travis/colinmeinke/universal-redux-router.svg)
81-

examples/basic/client.js

+5-11
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
11
import React from 'react';
22
import { render } from 'react-dom';
33
import { Provider } from 'react-redux';
4-
import { combineReducers, createStore } from 'redux';
5-
import { Router, updateUrl, urlReducer as url } from '../../src';
4+
import { Router } from '../../src';
65

76
import routes from './common/routes';
87

9-
const initialState = window.__INITIAL_STATE__;
8+
import createStore from './common/createStore';
109

11-
const reducer = combineReducers({ url });
10+
const { hash, pathname, search } = window.location;
11+
const url = pathname + search + hash;
1212

13-
const store = createStore( reducer, initialState );
14-
15-
window.addEventListener( 'popstate', () => {
16-
const { hash, pathname, search } = window.location;
17-
const url = pathname + search + hash;
18-
store.dispatch( updateUrl( url ));
19-
});
13+
const store = createStore({ url });
2014

2115
render(
2216
<Provider store={ store }>

examples/basic/common/actions/name.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const UPDATE_NAME = 'UPDATE_NAME';
2+
3+
export const updateName = name => ({ type: UPDATE_NAME, name });

examples/basic/common/components/About.js examples/basic/common/components/About/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import Nav from './Nav';
2+
import Nav from '../Nav';
33

44
const About = () => (
55
<div>
+7-9
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import React from 'react';
2-
import Nav from './Nav';
1+
import { connect } from 'react-redux';
32

4-
const Hello = ({ name }) => (
5-
<div>
6-
<h1>Hello { name }</h1>
7-
<Nav />
8-
</div>
9-
);
3+
import Hello from './Hello/index';
104

11-
export default Hello;
5+
const mapStateToProps = ({ name }) => ({ name });
6+
7+
const HelloContainer = connect( mapStateToProps )( Hello );
8+
9+
export default HelloContainer;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
import Nav from '../Nav';
3+
4+
const Hello = ({ name }) => (
5+
<div>
6+
<h1>Hello { name }</h1>
7+
<Nav />
8+
</div>
9+
);
10+
11+
export default Hello;

examples/basic/common/components/Home.js examples/basic/common/components/Home/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import Nav from './Nav';
2+
import Nav from '../Nav';
33

44
const Home = () => (
55
<div>

examples/basic/common/components/Nav.js

-16
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import qs from 'query-string';
2+
import React from 'react';
3+
import { Link } from '../../../../../src';
4+
5+
const name = 'Colin';
6+
7+
const Nav = () => (
8+
<nav>
9+
<Link to="/">Home</Link>
10+
<Link to="/about">About</Link>
11+
<Link to={[ 'hello', { name }]}>
12+
Hello
13+
</Link>
14+
</nav>
15+
);
16+
17+
export default Nav;

examples/basic/common/components/NotFound.js examples/basic/common/components/NotFound/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import Nav from './Nav';
2+
import Nav from '../Nav';
33

44
const NotFound = () => (
55
<div>

examples/basic/common/components/Page.js examples/basic/common/components/Page/index.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22

3-
const Page = ({ app, initialState }) => {
3+
const Page = ({ app }) => {
44
return (
55
<html lang="en">
66
<head>
@@ -12,9 +12,6 @@ const Page = ({ app, initialState }) => {
1212
className="app"
1313
dangerouslySetInnerHTML={{ __html: app }}
1414
/>
15-
<script
16-
dangerouslySetInnerHTML={{ __html: `window.__INITIAL_STATE__ = ${ JSON.stringify( initialState )}` }}
17-
/>
1815
<script defer src="/react.js" />
1916
<script defer src="/redux.js" />
2017
<script defer src="/react-redux.js" />

examples/basic/common/createStore.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { applyMiddleware, createStore } from 'redux';
2+
import { getState, routerMiddleware, routerReducer } from '../../../src';
3+
4+
import * as reducers from './reducers';
5+
import routes from './routes';
6+
7+
const reducer = routerReducer( reducers );
8+
9+
export default ({ isServer = false, url = '/' } = {}) => {
10+
const state = getState( url, routes, reducer );
11+
const middleware = applyMiddleware( routerMiddleware( routes, { isServer }));
12+
return createStore( reducer, state, middleware );
13+
};
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import name from './name';
2+
3+
export { name };
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { UPDATE_NAME } from '../actions/name';
2+
3+
export default function name ( state = '', action ) {
4+
switch ( action.type ) {
5+
case UPDATE_NAME:
6+
return action.name;
7+
default:
8+
return state;
9+
}
10+
}

examples/basic/common/routes.js

+8-16
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
1-
import qs from 'query-string';
21
import React from 'react';
32

3+
import { updateName } from './actions/name';
4+
45
import About from './components/About';
56
import Hello from './components/Hello';
67
import Home from './components/Home';
78
import NotFound from './components/NotFound';
89

9-
const routes = url => {
10-
const path = url.split( '?' )[ 0 ];
11-
const query = qs.parse( qs.extract( url ));
12-
13-
switch ( path ) {
14-
case '/about':
15-
return <About />;
16-
case '/hello':
17-
return <Hello name={ query.name } />;
18-
case '/':
19-
return <Home />;
20-
default:
21-
return <NotFound />;
22-
}
23-
};
10+
const routes = [
11+
[ 'about', <About /> ],
12+
[ 'hello', { name: updateName }, <Hello /> ],
13+
[ '/', <Home /> ],
14+
[ '*', <NotFound /> ],
15+
];
2416

2517
export default routes;

examples/basic/package.json

+12-12
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
"start": "babel-node ./server.js"
55
},
66
"dependencies": {
7-
"babel-cli": "^6.3.17",
8-
"babel-core": "^6.3.17",
9-
"babel-plugin-transform-react-jsx": "^6.3.13",
7+
"babel-cli": "^6.4.5",
8+
"babel-core": "^6.4.5",
9+
"babel-plugin-transform-object-rest-spread": "^6.3.13",
10+
"babel-plugin-transform-react-jsx": "^6.4.0",
1011
"babel-preset-es2015": "^6.3.13",
11-
"express": "^4.13.3",
12-
"query-string": "^3.0.0",
13-
"react": "^0.14.3",
14-
"react-dom": "^0.14.3",
15-
"react-redux": "^4.0.1",
16-
"redux": "^3.0.5"
12+
"express": "^4.13.4",
13+
"react": "^0.14.7",
14+
"react-dom": "^0.14.7",
15+
"react-redux": "^4.1.1",
16+
"redux": "^3.1.2"
1717
},
1818
"repository": {
1919
"type": "git",
@@ -24,11 +24,11 @@
2424
},
2525
"license": "ISC",
2626
"devDependencies": {
27-
"babel-loader": "^6.2.0",
28-
"webpack": "^1.12.9"
27+
"babel-loader": "^6.2.1",
28+
"webpack": "^1.12.12"
2929
},
3030
"babel": {
31-
"plugins": [ "transform-react-jsx" ],
31+
"plugins": [ "transform-object-rest-spread", "transform-react-jsx" ],
3232
"presets": [ "es2015" ]
3333
}
3434
}

examples/basic/server.js

+8-20
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,33 @@
11
import express from 'express';
22
import path from 'path';
3-
import qs from 'query-string';
43
import React from 'react';
54
import { renderToStaticMarkup, renderToString } from 'react-dom/server';
6-
import { connect, Provider } from 'react-redux';
7-
import { combineReducers, createStore } from 'redux';
8-
import { Router, updateUrl, urlReducer as url } from '../../src';
5+
import { Provider } from 'react-redux';
6+
import { changePageTo, Router } from '../../src';
97

108
import routes from './common/routes';
119

10+
import createStore from './common/createStore';
11+
1212
import Page from './common/components/Page';
1313

1414
const app = express();
1515

16-
const reducer = combineReducers({ url });
17-
1816
const handleRender = ( req, res ) => {
19-
const query = req.query ? `?${ qs.stringify( req.query )}` : null;
20-
21-
const store = createStore( reducer );
22-
23-
store.dispatch( updateUrl( req.path + query ));
17+
const store = createStore({ isServer: true, url: req.url });
2418

2519
res.send( renderFullPage(
2620
renderToString(
2721
<Provider store={ store }>
2822
<Router routes={ routes } />
2923
</Provider>
30-
),
31-
store.getState()
24+
)
3225
));
3326
};
3427

35-
const renderFullPage = ( app, initialState ) => {
28+
const renderFullPage = ( app ) => {
3629
return '<!DOCTYPE html>' +
37-
renderToStaticMarkup(
38-
<Page
39-
app={ app }
40-
initialState={ initialState }
41-
/>
42-
);
30+
renderToStaticMarkup( <Page app={ app } /> );
4331
};
4432

4533
app.get( '/react.js', ( req, res ) => {

0 commit comments

Comments
 (0)