GithubHelp home page GithubHelp logo

hapi-react-redux's Introduction

hapi-react-redux

hapi plugin for supporting universal rendering on both server and client

Build Status Coverage Status peerDependencies Status JavaScript Style Guide

Upgraded to be compatible with Hapi 17!

The plugin has now been updated to be compatible with hapi 17! if you are on an older version of hapi, stick with version ^2

Version ^3 is for hapi 17.

Why?

yes you could just write a module, import it, and re-use it in handlers and what not. but why not follow hapi's nice plugin architecture and make it easy?

Example app

you can find an example implementation using the event brite api at hapi-react-redux-example. The app uses this plugin with hapi to render a 2 screen app to illustrate route fetching and some basic rednering. It uses webpack to compile both the client and server side files. There are many ways to do a universal js app, but this will get you started if need be.

Usage

hapi-react-redux tries to be un-opinionated where possible. In a few places for the sake of ease of use, a few constraints are in place for the top level component of your application. The pattern for the plugin itself is modeled after the wonderful vision module for rendering views.

Register the plugin and configure it

import HapiReactRedux from 'hapi-react-redux'
import configureStore from 'path/to/configure/store/function'
const server = new Hapi.Server()
await server.register(HapiReactRedux)
server.hapiReactRedux({
  routes : clientRoutes, // routes for react-router-config, see examples below
  layout : layout, // layout file, see more below
  configureStore, // should be a function that configures the redux store
  config : { // any app config you want passed to the client side app, should be process.env values most likely :)
    value: '1234'
  },
  assets : { // any assets you want to use in your layout file, styles and scripts shown below are arbitrary
    styles: {
    },
    scripts: {
    }
  }

this registers the plugin and configures it for use.

Options

Routes

These are the routes for use in react router that comply with the route shape found in react-router-config

import React from 'react'
import App from './Root'
import Home from './Home'
import Search from './Search'

const NotFound = ({ staticContext }) => {
  staticContext.statusCode = 404
  return <p>Not found</p>
}

const routes = [
  {
    component: App,
    routes: [
      {
        path: '/',
        exact: true,
        component: Home
      },
      {
        path: '/search',
        component: Search
      },
      {
        component: NotFound
      }
    ]
  }
]

export default routes

this will allow you to set the routes in hapi-react-redux and on your client-side entry point.

Layout

For server rendering to work, you need a layout file for other parts of the markup that are not directly rendered by react.

below is a sample layout from the tests:

import React, { Component } from 'react'
import PropTypes from 'prop-types'

export default class Layout extends Component {
  static propTypes = {
    assets: PropTypes.object,
    config: PropTypes.object,
    content: PropTypes.string,
    state: PropTypes.string
  }

  render () {
    const { assets, content, state } = this.props
    return (
      <html lang='en-us'>
        <head>
          {Object.keys(assets.styles).map((style, key) =>
            <link href={`${assets.styles[style]}`} key={key} rel='stylesheet' type='text/css' charSet='UTF-8' />
          )}
        </head>
        <body>
          <div id='react-root' dangerouslySetInnerHTML={{__html: content}} />
          <script type='application/json' dangerouslySetInnerHTML={{ __html: `window.__data=${state}` }} charSet='UTF-8' />
          {Object.keys(assets.scripts).map((script, key) =>
            <script src={assets.scripts[script]} key={key} async />
          )}
        </body>
      </html>
    )
  }
}

the layout file is written in react, and is passed the data you configure in assets and config. The result of the react-router rendering is passed into the layout as content. Lastly the state of the redux store for the request is stored in the state prop. It is up to you to make this available to your client side application. The data is serialized using the serialize-javascript module to protect against xss attacks.

if you are utilizing content security policies and inline scripts are not allowed, you will have to embed the data a little differently:

<script type="application/json" id="initialState" dangerouslySetInnerHTML={{ __html: state }} charSet="UTF-8" />

The script type allows this to pass through CSP without any issues.

Then in your client side entry point, instead of just accessing the data in the variable, you have to grab the script tag and parse it.

const preloadedState = JSON.parse(document.getElementById('initialState').textContent)

configureStore

This should be a function that returns your fully configured redux store. an example may look something like this:

import rootReducer from 'reducers'
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
export default function (preloadedState) {
  return createStore(
    rootReducer,
    preloadedState,
    applyMiddleware(
      thunk
    )
  )
}

assets

this should have the paths to any javascript and css files you want on the page. these will end up as props.assets in your layout file, so structure it any way you want, but assets at the top level is required.

config

this is any config you want to be made available to your client side app.

Use the h.hapiReactReduxRender method to respond to a request

server.route({
  method: 'GET',
  path: '/',
  handler(request, h) {
    return h.hapiReactReduxRender()
  }
})

The plugin decorates the response toolkit with the hapiReactReduxRender method. This will use the options configured for your application, such as routes from react router, etc. if you need to pass some additional data from the server in your controller, you can send an object to the method:

server.route({
  method: 'GET',
  path: '/',
  handler(request, h) {
    //fetch some data...
    return h.hapiReactReduxRender({
      user: {
        name: 'Homer'//this will be available to your application as well. more on this in a bit
      }
    })
  }
})

this data will be available in the serverContext section of your store.

Use the server handler

hapi also has server handlers, where you can assign a predefined handler to a route if you know it will be the same all the time.

server.route({
  method: 'GET',
  path: '/',
  handler: { hapiReactReduxHandler: {} }
})

this will call the h.hapiReactReduxRender for you in your controller mapped to that route. Note that you will not be able to send any server context with this method.

Fetching data for routes on the server and the client

Another constraint that this plugin imposes is the high level way that requests for data are made for routes.

Each component that needs data for a route needs to be in the RR handler hierarchy and have a static fetch method that returns a promise. The returned promise will be resolved/rejected when any requests are completed. Other than that, inside that method you can retrieve data any way that you like.

Example

static fetch = function(match, location, { dispatch, getState }) {
  // returns a promise
  return store.dispatch(fetchData()) // dispatch an async action
}

The reactRouterFetch module is used to call the static methods on the matched route handlers. it will call the fetch method with the react router match object, the location, and the redux dispatch and getState methods.

Reducers

This library provides a set of reducers to add to your store configuration to facilitate the server rendering process to add it's data to the redux store.

Auth

Hapi has an auth object that ends up being populated after a user has signed in. This object is added to the store during the rendering process. Simply include this reducer in your store and access it like you would any other data.

Config

Any configuration data your application needs is passed from the server to the client. The provided reducer adds it to the redux store for your app to use.

optionally, you can also set up your app to use config values from process.env. see the hapi-react-redux-example for examples.

Pre handlers

Hapi has a concept called route prerequisites. These are functions that execute before a route handler is invoked. To enable this data being available in your react app, a reducer is provided to add it to the store.

Server Context

If there is a case where you want to send some data in a response directly from the server, you can send this data to the render method provided. It will be added to the serverContext key of your store. this is populated when you pass data directly from your server handler to the render method.

Flux standard actions

The reducers and action creators included with this module try to adhere to flux standard actions spec https://github.com/acdlite/flux-standard-action

client side entry point

To accomplish universal rendering, there needs to be a symmetry between how the server renders the app and how the client renders the app. In order to accomplish this the app needs to:

  • embed the data from the server request into the rendered response, so that the app can pick up the preloaded state and use it.
  • go through the same rendering process as the server as far as react is concerned.
  • fetch data when transitioning from route to route.
import React from 'react'
import { hydrate } from 'react-dom'
import { Provider } from 'react-redux'
import { BrowserRouter as Router } from 'react-router-dom'
import { renderRoutes } from 'react-router-config'

import clientRoutes from 'routes/routeConfig'
import configureStore from 'store/configure-store'

const store = configureStore(window.__data)

hydrate(
  <Provider store={store}>
    <Router>
      {renderRoutes(routeConfig)}
    </Router>
  </Provider>,
  document.getElementById('root')
)

This example is using the same routes as before. It gets the data that was embedded in the response (which in the sample layout file was made available via window.data variable), and bootstraps the redux store with it.

fetching data between route transitions on the client side

There are multiple ways to accomplish this, and you may want to think about what your UX should be when the app is moving from page to page. reactRouterFetch is used internally to do a few things:

  • call react-router-config matchRoutes on the path
  • call fetch on any matched components to get the data

You may choose to do something similar on your client-side app. The react-router-fetch repo has some sample code for this sort of component.

hapi-react-redux's People

Contributors

dutradda avatar greenkeeper[bot] avatar kellyrmilligan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

hapi-react-redux's Issues

Version 10 of node.js has been released

Version 10 of Node.js (code name Dubnium) has been released! 🎊

To see what happens to your code in Node.js 10, Greenkeeper has created a branch with the following changes:

  • Added the new Node.js version to your .travis.yml
  • Replaced the old Node.js version in your .nvmrc with the new one

If you’re interested in upgrading this repo to Node.js 10, you can open a PR with these changes. Please note that this issue is just intended as a friendly reminder and the PR as a possible starting point for getting your code running on Node.js 10.

More information on this issue

Greenkeeper has checked the engines key in any package.json file, the .nvmrc file, and the .travis.yml file, if present.

  • engines was only updated if it defined a single version, not a range.
  • .nvmrc was updated to Node.js 10
  • .travis.yml was only changed if there was a root-level node_js that didn’t already include Node.js 10, such as node or lts/*. In this case, the new version was appended to the list. We didn’t touch job or matrix configurations because these tend to be quite specific and complex, and it’s difficult to infer what the intentions were.

For many simpler .travis.yml configurations, this PR should suffice as-is, but depending on what you’re doing it may require additional work or may not be applicable at all. We’re also aware that you may have good reasons to not update to Node.js 10, which is why this was sent as an issue and not a pull request. Feel free to delete it without comment, I’m a humble robot and won’t feel rejected 🤖


FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of nodemon is breaking the build 🚨

The devDependency nodemon was updated from 1.18.5 to 1.18.6.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

nodemon is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Release Notes for v1.18.6

1.18.6 (2018-11-05)

Bug Fixes

Commits

The new version differs by 1 commits.

  • 521eb1e fix: restart on change for non-default signals (#1409) (#1430)

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of react-router-dom is breaking the build 🚨

The devDependency react-router-dom was updated from 4.3.1 to 4.4.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

react-router-dom is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

An in-range update of cross-env is breaking the build 🚨

Version 3.2.0 of cross-env just got published.

Branch Build failing 🚨
Dependency cross-env
Current Version 3.1.4
Type devDependency

This version is covered by your current version range and after updating it in your project the build failed.

As cross-env is “only” a devDependency of this project it might not break production or downstream projects, but “only” your build or test tools – preventing new deploys or publishes.

I recommend you give this issue a high priority. I’m sure you can resolve this 💪


Status Details
  • continuous-integration/travis-ci/push The Travis CI build could not complete due to an error Details
Release Notes v3.2.0

<a name"3.2.0">

3.2.0 (2017-03-04)

Features

  • revamp: revamp the entire lib (backward compatible) (#63) (dad00c46)
Commits

The new version differs by 4 commits .

  • dad00c4 feat(revamp): revamp the entire lib (backward compatible) (#63)
  • e33a85c docs(README): Add doc for cross-var. (#58)
  • 5e590ec docs(README): added how to use cross-env to run npm sub-scripts (#53)
  • afdb2de docs(README): mention Bash on Windows (#49)

See the full diff.

Not sure how things should work exactly?

There is a collection of frequently asked questions and of course you may always ask my humans.


Your Greenkeeper Bot 🌴

An in-range update of react is breaking the build 🚨

There have been updates to the react monorepo:

    • The devDependency react was updated from 16.6.0 to 16.6.1.
  • The devDependency react-dom was updated from 16.6.0 to 16.6.1.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

This monorepo update includes releases of one or more dependencies which all belong to the react group definition.

react is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Release Notes for v16.6.1

16.6.1 (November 6, 2018)

React DOM

  • Fallback should not remount every time a promise resolves. (@acdlite in #14083)
  • Fix bug where Suspense keeps showing fallback even after everything finishes loading. (@acdlite in #14083)
  • Fix unresolved default props in lifecycle methods of a lazy component. (@gaearon in #14112)
  • Fix bug when recovering from an error thrown during complete phase. (@gaearon in #14104)

Scheduler (Experimental)

  • Switch from deadline object to shouldYield API. (@acdlite in #14025)
FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.