How to preload data with react-router v4?

2019-04-14 06:13发布

问题:

This is example from official docs (https://reacttraining.com/react-router/web/guides/server-rendering/data-loading):

 import { matchPath } from 'react-router-dom'

 // inside a request
 const promises = []
 // use `some` to imitate `<Switch>` behavior of selecting only
 // the first to match
 routes.some(route => {
   // use `matchPath` here
   const match = matchPath(req.url, route)
   if (match)
     promises.push(route.loadData(match))
   return match
 })

 Promise.all(promises).then(data => {
   // do something w/ the data so the client
   // can access it then render the app
 })

This documentation makes me very nervous. This code doesn't work. And this aproach doesn't work! How can I preload data in server?

回答1:

This Is what I have done - which is something I came up with from the docs.

routes.cfg.js

First setup the routes config in a way that can be used for the client-side app and exported to used on the server too.

export const getRoutesConfig = () => [
  {
    name: 'homepage',
    exact: true,
    path: '/',
    component: Dasboard
  },
  {
    name: 'game',
    path: '/game/',
    component: Game
  }
];

...

// loop through config to create <Routes>

Server

Setup the server routes to consume the config above and inspect components that have a property called needs (call this what you like, maybe ssrData or whatever).

// function to setup getting data based on routes + url being hit
async function getRouteData(routesArray, url, dispatch) {
  const needs = [];
  routesArray
    .filter((route) => route.component.needs)
    .forEach((route) => {
      const match = matchPath(url, { path: route.path, exact: true, strict: false });
      if (match) {
        route.component.needs.forEach((need) => {
          const result = need(match.params);
          needs.push(dispatch(result));
        });
      }
    });
  return Promise.all(needs);
}

....

// call above function from within server using req / ctx object
const store = configureStore();  
const routesArray = getRoutesConfig();
await getRouteData(routesArray, ctx.request.url, store.dispatch);
const initialState = store.getState();

container.js/component.jsx

Setup the data fetching for the component. Ensure you add the needs array as a property.

import { connect } from 'react-redux';

import Dashboard from '../../components/Dashboard/Dashboard';
import { fetchCreditReport } from '../../actions';

function mapStateToProps(state) {
  return { ...state.creditReport };
}

const WrappedComponent = connect(
  mapStateToProps,
  { fetchCreditReport }
)(Dashboard);

WrappedComponent.needs = [fetchCreditReport];

export default WrappedComponent;

Just a note, this method works for components hooked into a matching routes, not nested components. But for me this has always been fine. The component at route level does the data fetch, then the components that need it later either has it passed to them or you add a connector to get the data direct from the store.