Skip to content
This repository has been archived by the owner on Jul 4, 2018. It is now read-only.

Commit

Permalink
Added promiseMiddleware to automatically fire redux actions when usin…
Browse files Browse the repository at this point in the history
…g promises in an action creator

Cleaned up code a little bit and updated documentation to reflect the changes
  • Loading branch information
Jan Hoogeveen committed Feb 27, 2017
1 parent f984659 commit 64de598
Show file tree
Hide file tree
Showing 15 changed files with 80 additions and 72 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cf-kyt-starter-universal-redux",
"version": "1.3.1",
"version": "1.3.2",
"description": "A starter kyt with React, Redux, SSR and data fetching.",
"main": "index.js",
"scripts": {
Expand Down
11 changes: 8 additions & 3 deletions src/components/Count/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ export function Count(props) {
return (
<div>
<p>Count: <strong>{ props.count }</strong></p>
<p>Promise resolve count: <strong>{ props.promiseCount }</strong></p>
<p>Promise rejection count: <strong>{ props.rejectionCount }</strong></p>
<p>When loading this page (directly) for the first time it should show a value of:</p>
<p>When loading this page (directly) for the first time the count should show a value of:</p>
<ul className="list">
<li>
<strong>0</strong> when Redux works
Expand All @@ -31,6 +29,13 @@ export function Count(props) {
<strong>2</strong> when store hydration works
</li>
</ul>
<p>
Promise resolve count: <strong>{ props.promiseCount }</strong><br />
Promise rejection count: <strong>{ props.rejectionCount }</strong>
</p>
<p>When loading this page (directly) for the first time one of the promise counts
should be 1.</p>

<button
className={styles.countButton}
onClick={props.shouldIncrement}
Expand Down
10 changes: 0 additions & 10 deletions src/components/Count/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,6 @@
animation: load8 1.1s infinite linear;
color: red;
}
@-webkit-keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes load8 {
0% {
-webkit-transform: rotate(0deg);
Expand Down
12 changes: 6 additions & 6 deletions src/containers/Addons/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ export default function Addons() {
</p>
<p>
To tackle this problem we added a few lines of code in the Express routing methods
which search for the static method fetchData whenever it loads a component
through React-Router. In this method we allow you to write Promises.
These promises are guaranteed to be resolved before sending down the HTML response.
This means you can use the Redux dispatch API to call some
redux action creators and populate your redux store for your first render.
which search for the static array needs whenever it loads a component
through React-Router. In this array we allow you to list promises on which the
container depends. These promises are guaranteed to be resolved before sending
down the HTML response. This means you can list redux action creators and
populate your redux store for your first render.
</p>
<p>
You can find the code responsible for handling these promises in
<code>src/server/index.js</code> starting from line 65. If you want to see
<code>src/server/index.js</code> starting from lines 36 and 84. If you want to see
an example of how to use this in your components you can read the source of
<code>src/containers/Addons</code>. Please be aware that in order to resolve
these promises the component needs to be a top level component.
Expand Down
3 changes: 0 additions & 3 deletions src/containers/App/cf-logo.svg

This file was deleted.

7 changes: 5 additions & 2 deletions src/containers/App/styles.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
@import '../../shared-styles/variables';

*, :after, :before {
box-sizing: border-box;
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}

:global {
Expand Down
4 changes: 1 addition & 3 deletions src/containers/NotFound/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
import React from 'react';
import Helmet from 'react-helmet';

import styles from './styles.scss';

function NotFound() {
return (
<section>
<Helmet title="404 not found" />
<p className={styles.paragraph}>
<p >
We could not find the thing you were looking for. Sorry!
</p>
<p>However, if you check your network panel when loading this page or any other
Expand Down
2 changes: 0 additions & 2 deletions src/containers/NotFound/styles.scss

This file was deleted.

31 changes: 13 additions & 18 deletions src/containers/Redux/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import Helmet from 'react-helmet';

import { shouldIncrement } from '../../redux/reducers/modules/counter';

import { shouldIncrement, shouldPromiseIncrement } from '../../redux/reducers/modules/counter';
import ConnectedCount from '../../components/Count';

export class ReduxContainer extends Component {
Expand All @@ -19,9 +17,10 @@ export class ReduxContainer extends Component {
* This method gets called by Express before injecting the state of
* the Redux store in our first render
*/
static fetchData(dispatch) {
dispatch(shouldIncrement());
}
static needs = [
shouldIncrement,
shouldPromiseIncrement,
]

componentDidMount() {
this.props.shouldIncrement();
Expand All @@ -34,24 +33,18 @@ export class ReduxContainer extends Component {

<h3>Redux example</h3>
<p>
Below is a small component - found in <code>components/Count/</code>
which does little less than showing the value of the count reducer in Redux.
It is used to demonstrate the behaviour of the fetchData method in this container.
As explained further on the Addons page, a static fetchData method
will load async data on the server before rendering the page.
Below is a small component - found in <code>components/Count/</code> which
does little less than showing the value of the different count reducer values in Redux.
It is used to demonstrate the behaviour of the needs array in this container.
As explained further on the Addons page, a static needs array
will dispatch promises on the server and await their responses before rendering the page.
</p>
<p>
To demonstrate the usage of Redux-Thunks we&apos;ve made a small action creator
in <code>redux/reducers/modules/counter.js</code> called <code>shouldIncrement</code>
which will only increment the counter value if it has not reached the limit of
15 yet.
</p>
<p>
Keep in mind the counter is only showing a valid result when opening this page directly
from the server. If you do not see a value of 2, please check if refreshing
this page helps. If it does not, check if your JavaScript is
running client-side?
</p>
<ConnectedCount />

</section>
Expand All @@ -63,6 +56,8 @@ function mapStateToProps() {
return { };
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ shouldIncrement }, dispatch);
return bindActionCreators({
shouldIncrement,
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ReduxContainer);
6 changes: 0 additions & 6 deletions src/containers/Redux/styles.scss

This file was deleted.

23 changes: 23 additions & 0 deletions src/helpers/promiseMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export default function promiseMiddleware() {
return next => (action) => {
const { promise, type, ...rest } = action;

if (!promise) {
return next(action);
}

const SUCCESS = `${type}_FULFILLED`;
const REQUEST = `${type}_PENDING`;
const FAILURE = `${type}_FAILED`;
next({ ...rest, type: REQUEST });
return promise
.then((res) => {
next({ ...rest, res, type: SUCCESS });
return true;
})
.catch((error) => {
next({ ...rest, error, type: FAILURE });
return false;
});
};
}
2 changes: 1 addition & 1 deletion src/public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"short_name": "cf-kyt",
"name": "C°F Universal React Starter Kyt",
"name": "C°F Starter Kyt",
"background_color": "white",
"theme_color": "white",
"icons": [
Expand Down
5 changes: 2 additions & 3 deletions src/redux/store/index.dev.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import promiseMiddleware from 'redux-promise-middleware';

import promiseMiddleware from '../../helpers/promiseMiddleware';

import rootReducer from '../reducers';
import DevTools from '../containers/DevTools';


const enhancer = compose(
// Middleware you want to use in development:
applyMiddleware(thunk, promiseMiddleware()),
applyMiddleware(thunk, promiseMiddleware),
// Required! Enable Redux DevTools with the monitors you chose
DevTools.instrument(),
);
Expand Down
6 changes: 4 additions & 2 deletions src/redux/store/index.prod.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import promiseMiddleware from '../../helpers/promiseMiddleware';

import rootReducer from '../reducers';

const enhancer = compose(
// Middleware you want to use in development:
applyMiddleware(thunk),
// Middleware you want to use in production:
applyMiddleware(thunk, promiseMiddleware),
);


Expand Down
28 changes: 16 additions & 12 deletions src/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ app.use(compression());
// Setup the public directory so that we can serve static assets.
app.use(express.static(path.join(process.cwd(), KYT.PUBLIC_DIR)));

/**
* This method creates an array of promises to resolve before Express
* calls the render method, useful for loading async data
* Note: Redux-thunk action creators also work when passed as a need in
* the static needs array, as long as they are not async
*/
function fetchComponentData(dispatch, components, params) {
const needs = components.reduce((prev, current) => (current.needs || [])
.concat(prev)
, []);
const promises = needs.map(need => dispatch(need(params)));
return Promise.all(promises);
}

// Setup server side routing.
app.use((request, response) => {
const history = createMemoryHistory(request.originalUrl);
Expand Down Expand Up @@ -67,18 +81,8 @@ app.use((request, response) => {
// Fetch the components from the renderProps and when they have
// promises, add them to a list of promises to resolve before starting
// a HTML response
const promises = renderProps.components
.filter(component => !!component && !!component.fetchData)
.map(component => component.fetchData(store.dispatch, renderProps));

if (!promises.length) {
render();
} else {
Promise.all(promises)
.then(() => {
render();
});
}
fetchComponentData(store.dispatch, renderProps.components, renderProps.params)
.then(render);
} else {
response.status(404).send('Not found');
}
Expand Down

0 comments on commit 64de598

Please sign in to comment.