Skip to content

Commit 038fde1

Browse files
committed
feat(core): allow async action creators
Action creators attached to the routes (both to a url param or as an after action creator) can now return a promise. BREAKING CHANGE: getState now returns a promise Before: ```js const state = getState( url, routes, reducer ); const store = createStore( reducer, state, middleware ); ``` After: ```js getState( url, routes, reducer ).then( state => { const store = createStore( reducer, state, middleware ); }) ```
1 parent 2b12af2 commit 038fde1

Some content is hidden

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

50 files changed

+977
-170
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,9 @@ your Redux store.
169169
```js
170170
import { getState } from 'universal-redux-router';
171171

172-
const state = getState( url, routes, reducer );
173-
174-
const store = createStore( reducer, state, middleware );
172+
getState( url, routes, reducer ).then( state => {
173+
const store = createStore( reducer, state, middleware );
174+
});
175175
```
176176

177177
## Help make this better

examples/basic/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
client.dist.js
2+
client.dist.js.map
23
node_modules/

examples/basic/client.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ import { Router } from '../../src';
55

66
import routes from './common/routes';
77

8-
import createStore from './common/createStore';
8+
import configureStore from './common/configureStore';
99

1010
const { hash, pathname, search } = window.location;
1111
const url = pathname + search + hash;
1212

13-
const store = createStore({ url });
14-
15-
render(
16-
<Provider store={ store }>
17-
<Router routes={ routes } />
18-
</Provider>,
19-
document.querySelector( '.app' )
20-
);
13+
configureStore({ url }).then( store => {
14+
render(
15+
<Provider store={ store }>
16+
<Router routes={ routes } />
17+
</Provider>,
18+
document.querySelector( '.app' )
19+
);
20+
}).catch( console.error );
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { UPDATE_NAME, updateName } from './name';
2+
3+
export { UPDATE_NAME, updateName };

examples/basic/common/actions/name.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
export const UPDATE_NAME = 'UPDATE_NAME';
1+
const UPDATE_NAME = 'UPDATE_NAME';
22

3-
export const updateName = name => ({ type: UPDATE_NAME, name });
3+
const updateName = name => ({ type: UPDATE_NAME, name });
4+
5+
export { UPDATE_NAME, updateName };
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
const configureStore = ({ isServer = false, url = '/' } = {}) => new Promise(( resolve, reject ) => {
10+
11+
getState( url, routes, reducer ).then( state => {
12+
13+
const middleware = applyMiddleware(
14+
routerMiddleware( routes, { isServer })
15+
);
16+
17+
resolve( createStore( reducer, state, middleware ));
18+
19+
}).catch( reject );
20+
21+
});
22+
23+
export default configureStore;

examples/basic/common/createStore.js

-13
This file was deleted.
+5-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { UPDATE_NAME } from '../actions/name';
1+
import { UPDATE_NAME } from '../actions';
22

3-
export default function name ( state = '', action ) {
3+
const name = ( state = '', action ) => {
44
switch ( action.type ) {
55
case UPDATE_NAME:
66
return action.name;
77
default:
88
return state;
99
}
10-
}
10+
};
11+
12+
export default name;

examples/basic/package.json

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
{
22
"scripts": {
3-
"build": "webpack ./client.js ./client.dist.js --config ./webpack.config.js",
3+
"build": "webpack ./client.js ./client.dist.js --config ./webpack.config.babel.js",
44
"start": "babel-node ./server.js"
55
},
66
"dependencies": {
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",
11-
"babel-preset-es2015": "^6.3.13",
7+
"babel-cli": "^6.5.1",
8+
"babel-core": "^6.5.1",
9+
"babel-plugin-transform-object-rest-spread": "^6.5.0",
10+
"babel-plugin-transform-react-jsx": "^6.5.0",
11+
"babel-preset-es2015": "^6.5.0",
1212
"express": "^4.13.4",
1313
"react": "^0.14.7",
1414
"react-dom": "^0.14.7",
15-
"react-redux": "^4.1.1",
16-
"redux": "^3.1.2"
15+
"react-redux": "^4.4.0",
16+
"redux": "^3.3.1"
1717
},
1818
"repository": {
1919
"type": "git",
@@ -24,8 +24,8 @@
2424
},
2525
"license": "ISC",
2626
"devDependencies": {
27-
"babel-loader": "^6.2.1",
28-
"webpack": "^1.12.12"
27+
"babel-loader": "^6.2.2",
28+
"webpack": "^1.12.13"
2929
},
3030
"babel": {
3131
"plugins": [ "transform-object-rest-spread", "transform-react-jsx" ],

examples/basic/server.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,22 @@ import { changePageTo, Router } from '../../src';
77

88
import routes from './common/routes';
99

10-
import createStore from './common/createStore';
10+
import configureStore from './common/configureStore';
1111

1212
import Page from './common/components/Page';
1313

1414
const app = express();
1515

1616
const handleRender = ( req, res ) => {
17-
const store = createStore({ isServer: true, url: req.url });
18-
19-
res.send( renderFullPage(
20-
renderToString(
21-
<Provider store={ store }>
22-
<Router routes={ routes } />
23-
</Provider>
24-
)
25-
));
17+
configureStore({ isServer: true, url: req.url }).then( store => {
18+
res.send( renderFullPage(
19+
renderToString(
20+
<Provider store={ store }>
21+
<Router routes={ routes } />
22+
</Provider>
23+
)
24+
));
25+
}).catch( console.error );
2626
};
2727

2828
const renderFullPage = ( app ) => {
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import config from '../../webpack.config.babel';
2+
3+
export default {
4+
...config,
5+
externals: {
6+
...config.externals,
7+
redux: 'Redux',
8+
},
9+
};

examples/basic/webpack.config.js

-7
This file was deleted.

examples/real-world/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
client.dist.js
2+
client.dist.js.map
3+
node_modules/

examples/real-world/README.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Real world example
2+
3+
## Getting started
4+
5+
To run this example make sure you have first run `npm install`
6+
in the root directory. Then `cd` into `/examples/real-world` and:
7+
8+
```
9+
npm install
10+
npm run build
11+
npm start
12+
```
13+
14+
Then visit [http://localhost:3000/menu](http://localhost:3000/menu).

examples/real-world/client.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react';
2+
import { render } from 'react-dom';
3+
import { Provider } from 'react-redux';
4+
import { Router } from '../../src';
5+
6+
import routes from './common/routes';
7+
8+
import configureStore from './common/configureStore';
9+
10+
const { hash, pathname, search } = window.location;
11+
const url = pathname + search + hash;
12+
13+
configureStore({ url }).then( store => {
14+
render(
15+
<Provider store={ store }>
16+
<Router routes={ routes } />
17+
</Provider>,
18+
document.querySelector( '.app' )
19+
);
20+
}).catch( console.error );
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const DISHES_LOADED = 'DISHES_LOADED';
2+
const DISHES_LOADING = 'DISHES_LOADING';
3+
4+
const dishesLoaded = () => ({ type: DISHES_LOADED });
5+
6+
const dishesLoading = () => ({ type: DISHES_LOADING });
7+
8+
export { DISHES_LOADED, DISHES_LOADING, dishesLoaded, dishesLoading };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { countries } from '../config';
2+
3+
const UPDATE_COUNTRY = 'UPDATE_COUNTRY';
4+
5+
const updateCountry = ( country = countries[ 0 ]) => ({ type: UPDATE_COUNTRY, country });
6+
7+
export { UPDATE_COUNTRY, updateCountry };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import 'isomorphic-fetch';
2+
3+
import getLocation from '../../../../src/helpers/getLocation';
4+
5+
const UPDATE_DISHES = 'UPDATE_DISHES';
6+
7+
const getDishes = ({ country, isVegetarian }) => new Promise(( resolve, reject ) => {
8+
const to = [ 'api/dishes', { country, isVegetarian }];
9+
const { url } = getLocation( to );
10+
11+
fetch( `http://localhost:3000${ url }`, {
12+
method: 'GET',
13+
}).then( response => {
14+
return response.json();
15+
}).then(({ dishes }) => {
16+
resolve({ type: UPDATE_DISHES, dishes });
17+
}).catch( error => {
18+
reject( error );
19+
});
20+
});
21+
22+
export { UPDATE_DISHES, getDishes };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { DISHES_LOADED, DISHES_LOADING, dishesLoaded, dishesLoading } from './areDishesLoading';
2+
import { UPDATE_COUNTRY, updateCountry } from './country';
3+
import { UPDATE_DISHES, getDishes } from './dishes';
4+
import { UPDATE_IS_VEGETARIAN, updateIsVegetarian } from './isVegetarian';
5+
6+
export {
7+
DISHES_LOADED,
8+
DISHES_LOADING,
9+
UPDATE_COUNTRY,
10+
UPDATE_DISHES,
11+
UPDATE_IS_VEGETARIAN,
12+
dishesLoaded,
13+
dishesLoading,
14+
getDishes,
15+
updateCountry,
16+
updateIsVegetarian,
17+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const UPDATE_IS_VEGETARIAN = 'UPDATE_IS_VEGETARIAN';
2+
3+
const updateIsVegetarian = ( isVegetarian = false ) => ({
4+
isVegetarian,
5+
type: UPDATE_IS_VEGETARIAN,
6+
});
7+
8+
export { UPDATE_IS_VEGETARIAN, updateIsVegetarian };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { connect } from 'react-redux';
2+
3+
import Menu from './Menu/index';
4+
5+
const mapStateToProps = ({ areDishesLoading, country, dishes, isVegetarian }) => ({
6+
areDishesLoading,
7+
country,
8+
dishes,
9+
isVegetarian,
10+
});
11+
12+
export default connect( mapStateToProps, null )( Menu );
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import React from 'react';
2+
import { changePageTo, Link } from '../../../../../src';
3+
import getLocation from '../../../../../src/helpers/getLocation';
4+
5+
import { countries } from '../../config';
6+
7+
const Menu = ({ areDishesLoading, country, dishes, dispatch, isVegetarian }) => (
8+
<div>
9+
<h2>Dishes by country:</h2>
10+
<ul>
11+
{ countries.map(( c, i ) => (
12+
<li key={ i }>
13+
<Link
14+
style={{
15+
fontWeight: c === country ? 'bold' : undefined,
16+
textTransform: 'capitalize',
17+
}}
18+
to={[ 'menu', c, { isVegetarian }]}
19+
>
20+
{ c }
21+
</Link>
22+
</li>
23+
))}
24+
</ul>
25+
<form
26+
action={ getLocation([ 'menu', country ]).url }
27+
method="GET"
28+
>
29+
<label htmlFor="isVegetarian">
30+
<input
31+
checked={ isVegetarian }
32+
defaultChecked={ isVegetarian }
33+
defaultValue="true"
34+
id="isVegetarian"
35+
onChange={ e => {
36+
dispatch(
37+
changePageTo([ 'menu', country, { isVegetarian: e.target.checked }])
38+
);
39+
}}
40+
name="isVegetarian"
41+
type="checkbox"
42+
/>
43+
Only show vegetarian dishes
44+
</label>
45+
<button type="submit">
46+
Update
47+
</button>
48+
</form>
49+
<h2>Dishes:</h2>
50+
{ areDishesLoading ? <p>loading ...</p> : (
51+
<ul>
52+
{ dishes.map(({ name }, i ) => (
53+
<li key={ i }>
54+
<h3>{ name }</h3>
55+
</li>
56+
))}
57+
</ul>
58+
)}
59+
</div>
60+
);
61+
62+
export default Menu;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react';
2+
import { Link } from '../../../../../src';
3+
4+
const NotFound = () => (
5+
<div>
6+
<h1>Not found</h1>
7+
<Link to="/menu">
8+
Please go to the <strong>menu page</strong>
9+
</Link>
10+
</div>
11+
);
12+
13+
export default NotFound;

0 commit comments

Comments
 (0)