Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This is a starter boilerplate app I've put together using the following technolo
* [Redux](https://github.com/rackt/redux)'s futuristic [Flux](https://facebook.github.io/react/blog/2014/05/06/flux.html) implementation
* [Redux Dev Tools](https://github.com/gaearon/redux-devtools) for next generation DX (developer experience). Watch [Dan Abramov's talk](https://www.youtube.com/watch?v=xsSnOQynTHs).
* [Redux Router](https://github.com/rackt/redux-router) Keep your router state in your Redux store
* [react-fetcher](https://github.com/markdalgleish/react-fetcher) for route-based universal data fetching
* [ESLint](http://eslint.org) to maintain a consistent code style
* [redux-form](https://github.com/erikras/redux-form) to manage form state in Redux
* [lru-memoize](https://github.com/erikras/lru-memoize) to speed up form validation
Expand Down Expand Up @@ -86,7 +87,7 @@ We also spit out the `redux` state into a global `window.__data` variable in the

#### Server-side Data Fetching

We ask `react-router` for a list of all the routes that match the current request and we check to see if any of the matched routes has a static `fetchData()` function. If it does, we pass the redux dispatcher to it and collect the promises returned. Those promises will be resolved when each matching route has loaded its necessary data from the API server.
We ask `react-router` for a list of all the routes that match the current request and pass them to `react-fetcher` to check to see if any of the matched routes have a `@prefetch` or `@defer` decorator function. If it does, we pass the redux dispatcher to it and `react-fetcher` collects the promises returned. Those promises will be resolved when each matching route has loaded its necessary data from the API server.

#### Client Side

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@
"express-session": "^1.12.1",
"file-loader": "^0.8.4",
"history": "^1.13.0",
"hoist-non-react-statics": "^1.0.3",
"http-proxy": "^1.12.0",
"less": "^2.5.3",
"less-loader": "^2.2.1",
Expand All @@ -103,6 +102,7 @@
"react-bootstrap": "^0.27.3",
"react-document-meta": "^2.0.0",
"react-dom": "^0.14.1",
"react-fetcher": "^0.1.0",
"react-inline-css": "^2.0.0",
"react-redux": "^4.0.0",
"react-router": "^1.0.0-rc3",
Expand Down
8 changes: 3 additions & 5 deletions src/containers/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { isLoaded as isInfoLoaded, load as loadInfo } from 'redux/modules/info';
import { isLoaded as isAuthLoaded, load as loadAuth, logout } from 'redux/modules/auth';
import { InfoBar } from 'components';
import { pushState } from 'redux-router';
import connectData from 'helpers/connectData';
import { prefetch } from 'react-fetcher';
import config from '../../config';

function fetchData(getState, dispatch) {
@prefetch(({ getState, dispatch }) => {
const promises = [];
if (!isInfoLoaded(getState())) {
promises.push(dispatch(loadInfo()));
Expand All @@ -20,9 +20,7 @@ function fetchData(getState, dispatch) {
promises.push(dispatch(loadAuth()));
}
return Promise.all(promises);
}

@connectData(fetchData)
})
@connect(
state => ({user: state.auth.user}),
{logout, pushState})
Expand Down
9 changes: 6 additions & 3 deletions src/containers/Home/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export default class Home extends Component {
<li><a href="https://github.com/rackt/redux-router" target="_blank">Redux Router</a> Keep
your router state in your Redux store
</li>
<li><a href="https://github.com/markdalgleish/react-fetcher" target="_blank">react-fetcher</a> for route-based universal data fetching</li>
<li><a href="http://eslint.org" target="_blank">ESLint</a> to maintain a consistent code style</li>
<li><a href="https://github.com/erikras/redux-form" target="_blank">redux-form</a> to manage form state
in Redux
Expand Down Expand Up @@ -108,9 +109,11 @@ export default class Home extends Component {
<dt>Server-side data loading</dt>
<dd>
The <Link to="/widgets">Widgets page</Link> demonstrates how to fetch data asynchronously from
some source that is needed to complete the server-side rendering. <code>Widgets.js</code>'s
<code>fetchData()</code> function is called before the widgets page is loaded, on either the server
or the client, allowing all the widget data to be loaded and ready for the page to render.
some source that is needed to complete the server-side rendering.
<code>Widgets.js</code>'s <code>@defer</code> decorator is passed a function that is called
by <a href="https://github.com/markdalgleish/react-fetcher" target="_blank">react-fetcher</a>
before the widgets page is loaded, on either the server or the client, allowing all the widget
data to be loaded and ready for the page to render.
</dd>
<dt>Data loading errors</dt>
<dd>
Expand Down
14 changes: 7 additions & 7 deletions src/containers/Widgets/Widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@ import {connect} from 'react-redux';
import * as widgetActions from 'redux/modules/widgets';
import {isLoaded, load as loadWidgets} from 'redux/modules/widgets';
import {initializeWithKey} from 'redux-form';
import connectData from 'helpers/connectData';
import {defer} from 'react-fetcher';
import { WidgetForm } from 'components';

function fetchDataDeferred(getState, dispatch) {
@defer(({ getState, dispatch }) => {
if (!isLoaded(getState())) {
return dispatch(loadWidgets());
}
}

@connectData(null, fetchDataDeferred)
})
@connect(
state => ({
widgets: state.widgets.data,
Expand Down Expand Up @@ -60,8 +58,10 @@ class Widgets extends Component {
<p>
If you hit refresh on your browser, the data loading will take place on the server before the page is returned.
If you navigated here from another page, the data was fetched from the client after the route transition.
This uses the static method <code>fetchDataDeferred</code>. To block a route transition until some data is loaded, use <code>fetchData</code>.
To always render before loading data, even on the server, use <code>componentDidMount</code>.
This uses the <code>@defer</code> decorator
from <a href="https://github.com/markdalgleish/react-fetcher" target="_blank">react-fetcher</a>. To block a
route transition until some data is loaded, use <code>@prefetch</code>. To always render before loading data,
even on the server, use <code>componentDidMount</code>.
</p>
<p>
This widgets are stored in your session, so feel free to edit it and refresh.
Expand Down
29 changes: 0 additions & 29 deletions src/helpers/__tests__/connectData-test.js

This file was deleted.

61 changes: 0 additions & 61 deletions src/helpers/__tests__/getDataDependencies-test.js

This file was deleted.

24 changes: 0 additions & 24 deletions src/helpers/connectData.js

This file was deleted.

9 changes: 0 additions & 9 deletions src/helpers/getDataDependencies.js

This file was deleted.

15 changes: 8 additions & 7 deletions src/redux/middleware/transitionMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ROUTER_DID_CHANGE} from 'redux-router/lib/constants';
import getDataDependencies from '../../helpers/getDataDependencies';
import {getPrefetchedData, getDeferredData} from 'react-fetcher';

const locationsAreEqual = (locA, locB) => (locA.pathname === locB.pathname) && (locA.search === locB.search);

Expand All @@ -10,24 +10,25 @@ export default ({getState, dispatch}) => next => action => {
}

const {components, location, params} = action.payload;
const locals = {getState, dispatch, location, params};
const promise = new Promise((resolve) => {

const doTransition = () => {
next(action);
Promise.all(getDataDependencies(components, getState, dispatch, location, params, true))
getDeferredData(components, locals)
.then(resolve)
.catch(error => {
// TODO: You may want to handle errors for fetchDataDeferred here
console.warn('Warning: Error in fetchDataDeferred', error);
// TODO: You may want to handle errors for @defer here
console.warn('Warning: Error in "defer" decorator function', error);
return resolve();
});
};

Promise.all(getDataDependencies(components, getState, dispatch, location, params))
getPrefetchedData(components, locals)
.then(doTransition)
.catch(error => {
// TODO: You may want to handle errors for fetchData here
console.warn('Warning: Error in fetchData', error);
// TODO: You may want to handle errors for @prefetch here
console.warn('Warning: Error in "prefetch" decorator function', error);
return doTransition();
});
});
Expand Down