react-hydrate
v0.9.1
Published
Generic data fetching and SSR hydration pattern for React.
Downloads
47
Readme
react-hydrate
Generic data fetching and SSR hydration pattern for React.
Features & Goals
- Co-locate data dependencies with your components
- Supports infinitely nested loaders
- Fetches requested data on the server and hydrates on the client for a fast startup
- Wraps components so users can easily define loading states for components
- Routing agnostic. Works with
react-router
v4. - Lightweight ~1.9kb
Related: react-hydrate-link - prefetch data for your next route using react-router v4.
Usage
Defining components
/**
* Projects.js
*/
import api from 'my-api'
import { hydrate } from 'react-hydrate'
import Project from './Project.js'
export default hydrate(
/**
* dataLoader receives component props
* and any state already in the store
*/
(props, state) => {
return api.fetchProjects().then(projects => {
return {
projects: projects
}
})
},
/**
* mapStateToProps receives the
* loaded data via `state` and any
* component props.
*
* You should return `false` here if
* the data needed is not yet availabe.
* If a falsy value is returned, it
* tells the library that the loader
* hasn't been run yet or hasn't
* yet resolved.
*/
(state, props) => {
return state.projects ? {
projects: state.projects
} : false
}
)(({ loading, data, ...inheritedProps }) => {
/**
* Component is always passed a loading
* prop that represents the status of their
* dataLoader function
*/
return loading ? (
<div>Loading data...</div>
) : (
data.projects.map(project => <Project {...project} key={project.slug}>)
)
})
/**
* App.js
*/
import React from 'react'
import Projects from './Projects.js'
export default props => (
<div>
<Projects />
</div>
)
Creating root app
import React from 'react'
import { render } from 'react-dom'
import { BrowserRouter as Router } from 'react-router-dom'
import { Tap } from 'react-hydrate'
import App from './App'
render((
<Router>
<Tap hydrate={window.__hydrate || null}>
<App />
</Tap>
</Router>
), document.getElementById('root'))
Server
import React from 'react'
import { renderToString } from 'react-dom/server'
import { StaticRouter: Router } from 'react-router'
import { Tap, createStore } from 'react-hydrate'
import { asyncRender } from 'react-hydrate/dist/server'
import App from './App.js'
app.use((req, res) => {
const ctx = {}
const store = createStore({})
const Root = (
<Router location={req.url} context={ctx}>
<Tap hydrate={store}>
<App />
</Tap>
</Router>
)
asyncRender(Root).then(() => {
const state = store.getState()
const content = renderToString(Root)
if (ctx.url) {
res.writeHead(302, {
Location: ctx.url
})
res.end()
} else {
res.send(`
<!DOCTYPE html>
<html>
<head></head>
<body>
${content}
<script>
window.__hydrate = ${JSON.stringify(state)}
</script>
<script src="/index.js"></script>
</body>
</html>
`)
res.end()
store.clearState()
}
})
})
Dependencies
- react-tree-walker: Walk a React element tree. by @ctrlplusb
MIT License