![Oracle Drags Its Feet in the JavaScript Trademark Dispute](https://cdn.sanity.io/images/cgdhsj6q/production/919c3b22c24f93884c548d60cbb338e819ff2435-1024x1024.webp?w=400&fit=max&auto=format)
Security News
Oracle Drags Its Feet in the JavaScript Trademark Dispute
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
hapi-react-redux
Advanced tools
hapi plugin for supporting universal rendering on both server and client
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.
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?
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.
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.
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.
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)
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
)
)
}
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.
this is any config you want to be made available to your client side app.
h.hapiReactReduxRender
method to respond to a requestserver.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.
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.
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.
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.
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.
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.
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.
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.
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.
The reducers and action creators included with this module try to adhere to flux standard actions spec https://github.com/acdlite/flux-standard-action
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:
react
is concerned.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.
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:
react-router-config
matchRoutes
on the pathYou 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.
FAQs
Hapi rendering plugin for react and redux apps
We found that hapi-react-redux demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Security News
The Linux Foundation is warning open source developers that compliance with global sanctions is mandatory, highlighting legal risks and restrictions on contributions.
Security News
Maven Central now validates Sigstore signatures, making it easier for developers to verify the provenance of Java packages.