Skip to content
Generic data fetching, caching, and SSR hydration pattern for React
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
example
package
README.md

README.md

react-hydrate

Generic data fetching and SSR hydration pattern for React.

js-standard-style

Features & Goals

  1. Co-locate data dependencies with your components
  2. Supports infinitely nested loaders
  3. Fetches requested data on the server and hydrates on the client for a fast startup
  4. Wraps components so users can easily define loading states for components
  5. Routing agnostic. Works with react-router v4.
  6. 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

MIT License

You can’t perform that action at this time.