create-render-4r

3.1.2 • Public • Published

Universal render for React+ Build Status

Express.js middleware to render a 4r app server-side:

Diagram: Universal Web Apps & create-render-4r

Features

Example Universal Web App

Demonstrates using this renderer in a working universal app.

Install

Add the module to package.json:

npm install create-render-4r --save
 
# Save peer dependencies for production (otherwise they're only dev dependencies): 
npm install radium@0.15.x react@0.14.x react-document-meta@2.x react-dom@0.14.x react-redux@4.x react-router@1.x redux@3.x --save

Upgrading

Breaking changes are indicated by major versions. See UPGRADING

Usage

Basic usage in an Express server.js:

var express = require('express');
var createRender4r = require('create-render-4r');
 
var app = express();
 
// These are unique to your own app.
var routes = require('./my-routes');
var loadStore = require('./my-load-store');
var layoutHtml = require('./my-layout-html');
 
// Create the render middleware.
var render4r = createRender4r({
  routes:       routes,
  loadStore:    loadStore,
  layoutHtml:   layoutHtml
});
 
// Add the render for all requests.
app.use(render4r);
 
var PORT = process.env.PORT || 3000;
app.listen(PORT, function () {
  console.log('Server listening on', PORT);
});

Example server.js

API

createRender4r()

This function is used to generate the Express.js middleware.

It accepts a single argument, an object:

createRender4r({ routes, loadStore, layoutHtml, decorateResponse })

routes

(Required) The <Router/> component

loadStore

(Required) A function taking initial state, returning the Redux store; created with Redux createStore

var Redux = require('redux');
var createStore = Redux.createStore;
var combineReducers = Redux.combineReducers;
 
var reducers = './my-reducers';
 
function loadStore(initialState) {
  return createStore(combineReducers(reducers), initialState);
}

layoutHtml

(Required) An HTML template function; this sample uses ES2015 module & template string syntx:

function layoutHtml(componentHTML, cleanInitialState, documentMeta) {
  return `
    <!DOCTYPE html>
    <html>
      <head>
        ${documentMeta}
 
        <script type="application/javascript">
          window.__INITIAL_STATE__ = ${cleanInitialState};
        </script>
      </head>
      <body>
        <div id="react-view">${componentHTML}</div>
 
        <script type="application/javascript" src="/bundle.js"></script>
      </body>
    </html>
  `;
}

decorateResponse

(Optional) A side-effect function to update the response based on state:

function decorateResponse(res, state) {
  /*
  Example: set 404 response status when the item couldn't be fetched,
    while the app still renders a nice Not Found message in the UI.
  */
  var errText = state.item.get('error');
  if (errText && /^404/.exec(errText)) {
    res.status(404)
  }
}

Example createRender4r()

Server-side async data loading

Per-component data loading for the current route.

fetchData()

Define this static (class) method on React components to enable Promise-based server-side fetching. You'll need to use a universal library like isomorphic-fetch within redux-thunk async action creators so they will work equivalently on the server-side & in web browsers.

fetchData(dispatch, props)

  • dispatch (required) the Redux store's dispatcher
  • props (required) the component's props

Must return a Promise to await completetion of the fetch. (This is what Redux thunk does.)

static fetchData(dispatch, props) {
  return dispatch(ItemActions.getItem(props.params.id));
}

Example fetchData()

sagasToRun()

Define this static (class) method on React components to enable Generator-based server-side fetching via Redux sagas.

sagasToRun(dispatch, props)

  • dispatch (required) the Redux store's dispatcher
  • props (required) the component's props

Must return an array of arrays of arguments for middleware.run.

static sagasToRun(dispatch, props) {
  return [
    [fetchThingsSaga],
    [verifyAuthSaga, { userId: props.params.authUserId }]
  ];
}

Additionally, the Redux store returned by loadStore() must expose the Saga middleware as store.sagaMiddleware:

// …
import createSagaMiddleware       from 'redux-saga';
// …
 
export default function loadStore(...createStoreRestParams) {
  const sagaMiddleware            = createSagaMiddleware(...sagas);
  // …
  // This accessor must be added for Saga middleware to be used server-side:
  store.sagaMiddleware            = sagaMiddleware;
  return store;
}

Absolute URLs

Frequently, app code will need its absolute URL, canonical hostname & protocol to render links or make API requests.

This module includes a sourceRequest reducer to handle this state. It is captured on the server for each request as the Redux store is initialized.

Example state.sourceRequest.host values:

  • localhost:3000
  • example.com
  • api.example.com:8443
  • velvet-glacier-1234.herokuapp.com

Example state.sourceRequest.protocol values:

  • https
  • http

To use these values in an app:

  1. Add this module's reducers to your store:
import { combineReducers } from 'redux'
import { reducers as cr4rReducers } from 'create-render-4r'
 
const rootReducer = combineReducers(
  Object.assign(
    cr4rReducers,
    // …& the app's reducers
  )
)

Example reducers/index.js

  1. Then access it in the Redux state:
// `store` is the Redux store
const state = store.getState();
const proto = state.sourceRequest.protocol;
const host = state.sourceRequest.host;

Example actions/counter.js

Readme

Keywords

none

Package Sidebar

Install

npm i create-render-4r

Weekly Downloads

1

Version

3.1.2

License

MIT

Last publish

Collaborators

  • mars