Redux Fetch provides universal data fetching bindings for applications built with React, React Router, and Redux. It relies on promises implemented in your application to determine whether the state to render route components matching a location is fulfilled.
Install the correct versions of each package, which are listed by the command:
npm info "@flowio/redux-fetch" peerDependencies
Linux / OSX users can simply run:
npm info "@flowio/redux-fetch@latest" peerDependencies --json | command sed 's/[\{\},]//g ; s/: /@/g' | xargs npm install --save "@flowio/redux-fetch@latest"
Windows users can either install all the peer dependencies manually, or use the install-peerdeps
cli tool.
npm install -g install-peerdeps
install-peerdeps @flowio/redux-fetch
This assumes that you’re using npm package manager with a module bundler like Webpack or Browserify to consume CommonJS modules.
The first thing that you have to do is give the Redux Fetch reducer to Redux. You will only have to do this once, no matter how many fetch containers your application uses.
import { createStore, combineReducers } from 'redux';
import { reducer as fetchReducer } from '@flowio/redux-fetch';
const reducer = combineReducers({
// ...Your other reducers here
fetch: fetchReducer,
});
export default function configureStore(initialState) {
return createStore(reducer, initialState);
}
Decorate your route components with withFetch()
and provide a function that returns a promise that is settled after the application state is updated with the data required before rendering them.
import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { withFetch } from '@flowio/redux-fetch';
import { fetchOneThing, fetchTwoThings } from './app/actions';
import { getThings } from './app/selectors';
import { ThingComponent } from './app/components';
const fetchAsyncState = (dispatch/*, getState, routerState */) => Promise.all([
dispatch(fetchOneThing()),
dispatch(fetchTwoThings()),
]);
const mapStateToProps = (state) => ({
things: getThings(state),
});
const enhance = compose(
withFetch(fetchAsyncState),
connect(mapStateToProps),
);
export default enhance(ThingComponent);
On the server side you should match the routes to a location, fulfill data requirements, and finally render.
import React from 'react';
import { renderToString } from 'react-dom/server';
import { match, RouterContext } from 'react-router';
import { Provider } from 'react-redux';
import { fetchRouteData, FetchRootContainer } from '@flowio/redux-fetch';
import store from './app/store';
import routes from './app/routes';
// Render the application server-side for a given path:
export default (location) => {
return new Promise((resolve, reject) => {
// Match routes to a location
match({ routes, location }, (matchError, redirectLocation, renderProps) => {
// Fulfill data requirements
store.dispatch(fetchRouteData(renderProps)).then(() => {
// Initial state passed to the client-side
const state = store.getState();
const html = renderToString(
<Provider store={store}>
<FetchRootContainer routerProps={renderProps}>
<RouterContext {...renderProps} />
</FetchRootContainer>
</Provider>
);
resolve({ html, state });
});
});
});
}
On the client-side you should use the useFetch
router middleware and rehydrate the Redux store with the state from the server.
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { applyRouterMiddleware, browserHistory, Router } from 'react-router';
import { useFetch } from '@flowio/redux-fetch';
import configureStore from './app/configureStore';
import configureRoutes from './app/configureRoutes';
// Render the app client-side to a given container element:
export default (container) => {
// Your server rendered response needs to expose the state of the store, e.g.
// <script>
// window.__INITIAL_STATE___ = JSON.stringify(state);
// </script>
const store = configureStore(window.__INITIAL_STATE___);
const routes = configureRoutes();
render(
<Provider store={store}>
<Router
routes={routes}
history={browserHistory}
render={applyRouterMiddleware(useFetch())} />
</Provider>,
container
);
}
A higher-order component that lets route components encode their data requirements.
-
fetchAsyncState(dispatch: Function, getState: Function, routerState: RouterState): Promise
: A function that returns a promise that is resolved when the Redux store is updated with the data required to render the decorated component or rejected otherwise.The three arguments passed to the
fetchAsyncState
function are:-
dispatch
: A dispatcher used to broadcast payloads to change the application state. -
getState
: A function that returns the current state tree of your application. -
routerState
: Properties normally injected intoRouterContext
that represent the current state of a router.
-
A React component class that renders your component.
All the original static properties of your component are hoisted.
fetchAsyncState
: The function passed towithFetch()
to resolve data requirements for your component.
All the original static methods of your component are hoisted.
-
It needs to be invoked two times. The first time with its arguments as described above, and a second time, with the component:
withFetch(fetchAsyncState)(MyComponent)
. -
It does not modify the passed React component. It returns a new component that you should use instead.
-
The static
fetchAsyncState
function is used to resolve the data required before rendering the matched route components for a location.
A React component that attempts to fulfill the data required in order to render matched route components in the component hierarchy below.
-
routerProps: RouterState
: The React Router properties normally injected intoRouterContext
that represent the current state of the router. -
[forceInitialFetch: Boolean]
: If supplied and set totrue
, a request for data will always be made to the server regardless of whether data on the client is available to immediately fulfill the data requirements. -
[renderLoading: Function]
: Redux Fetch renders the loading state whenever it cannot immediately fulfill data needed to render. By default, nothing is rendered while loading data for the initial render. If a previous component was fulfilled and rendered, the default behavior is to continue rendering the previous view. You can change this behavior by supplying therenderLoading
property. ArenderLoading
callback can simulate the default behavior by returningundefined
. Notice that this is different from arenderLoading
callback that returnsnull
, which would render nothing whenever data is loading, even if there was a previous view rendered. -
[renderFailure: Function]
: If an error occurs that prevents Redux Fetch from fetching the data required for rendering a component, nothing will be rendered by default. Error handling behavior can be configured by supplying a callback to therenderFailure
property. TherenderFailure
callback is called with the error. -
[renderSuccess: Function]
: When all data necessary to render becomes available, Redux Fetch will render the matched route components by default. However, you can change this behavior by supplying a callback to therenderSuccess
property. TherenderSuccess
callback is called with thechildren
to be rendered.
-
Views returned by
renderLoading
,renderFailure
, andrenderSuccess
are rendered outside the router context. -
Typically, you will not use the
renderSuccess
callback. It's exposed in case you need to hack something together.
A React Router middleware that attempts to fulfill the data required in order to render matched decorated route components in the component hierarchy below.
[options: Object]
: Same options available forFetchRootContainer
as props with the exception ofrouterProps
since it can be inferred.
An asynchronous action creator that fetches the data required before rendering matched route components when dispatched.
The arguments you should inject into fetchRouteData
are:
routerProps: RouterState
: Properties normally injected intoRouterContext
that represent the current state of a router.
This project, while far less complex, was inspired and borrows some concepts from Relay.