Skip to content
This repository has been archived by the owner on Aug 1, 2024. It is now read-only.
/ redux-fetch Public archive

Redux Fetch provides universal data fetching bindings for applications built with React, React Router, and Redux.

License

Notifications You must be signed in to change notification settings

flowcommerce/redux-fetch

Repository files navigation

Redux Fetch

Build Status NPM Version Downloads Greenkeeper badge

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.

Installation

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.

Usage

Configure Redux store with Redux Fetch

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);
}

Configure route components to fetch required data before rendering

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);

Configure server-side rendering

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 });
      });
    });
  });
}

Configure client-side rendering

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
  );
}

API Reference

withFetch(fetchAsyncState)

A higher-order component that lets route components encode their data requirements.

Arguments

  • 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 into RouterContext that represent the current state of a router.

Returns

A React component class that renders your component.

Static Properties

All the original static properties of your component are hoisted.

Static Methods

  • fetchAsyncState: The function passed to withFetch() to resolve data requirements for your component.

All the original static methods of your component are hoisted.

Remarks

  • 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.

FetchRootContainer

A React component that attempts to fulfill the data required in order to render matched route components in the component hierarchy below.

Props

  • routerProps: RouterState: The React Router properties normally injected into RouterContext that represent the current state of the router.

  • [forceInitialFetch: Boolean]: If supplied and set to true, 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 the renderLoading property. A renderLoading callback can simulate the default behavior by returning undefined. Notice that this is different from a renderLoading callback that returns null, 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 the renderFailure property. The renderFailure 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 the renderSuccess property. The renderSuccess callback is called with the children to be rendered.

Remarks

  • Views returned by renderLoading, renderFailure, and renderSuccess 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.

useFetch([options])

A React Router middleware that attempts to fulfill the data required in order to render matched decorated route components in the component hierarchy below.

Arguments

  • [options: Object]: Same options available for FetchRootContainer as props with the exception of routerProps since it can be inferred.

fetchRouteData(routerProps)

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 into RouterContext that represent the current state of a router.

Acknowledgments

This project, while far less complex, was inspired and borrows some concepts from Relay.

Related projects

License

MIT License

About

Redux Fetch provides universal data fetching bindings for applications built with React, React Router, and Redux.

Resources

License

Stars

Watchers

Forks

Packages

No packages published