1

I'm trying to migrate to v4 of React Router and I'm stuck with the prefetch data. The latests versions we used to have match function.

One of the returning parameters was renderProps but now I can't use match so I don't know how to prefetch data. The documentation shows how to load data but doesn't works for me because I'm using the new version of react-router-redux to sync the store.

Here's the code the client:

return (
  <MuiThemeProvider muiTheme={letgoMuiTheme}>
    <Provider store={store}>
      <IntlProvider>
        { routes(history, store) }
      </IntlProvider>
  </Provider>
</MuiThemeProvider>
);

Routes:

export default function routes(history) {
  return (
    <ConnectedRouter history={history}>
      <Switch>
        <Route exact path="/" component={App} />
        <Route path="/:lang/chat" component={Chat} />
        ...
        <Route path="*" component={NotFound} status={404} />
      </Switch>
    </ConnectedRouter>
  );
}

Server:

if (context.url) {
  res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else {
  const render = () => {
    const body = renderToString(
      <StaticRouter
        location={req.url}
        context={context}
      >
        <MuiThemeProvider muiTheme={muiTheme}>
          <Provider store={store}>
            <IntlProvider>
              { routes(history, store) }
            </IntlProvider>
          </Provider>
        </MuiThemeProvider>,
      </StaticRouter>
    );

    res.setHeader('content-language', locale);
    res.render('index', {
      head,
      body,
      locale,
      state: stringState,
      ...
    });
  };

  Promise.all(
    prefetchData(renderProps, store),
  )
  .then(render)
  .catch(prefetchError => next(prefetchError));
}

That's my old server file:

    match({ history, routes, location: req.url }, (error, redirectLocation, renderProps) => {
      if (error) {
        throw error;
      } else if (redirectLocation) {
        res.redirect(302, redirectLocation.pathname + redirectLocation.search);
      } else if (renderProps) {
        // Render react components once store is initializaed and return HTTP response
        const render = () => {
          const body = renderToString(
            <MuiThemeProvider muiTheme={muiTheme}>
              <Provider store={store}>
                <IntlProvider>
                  <RouterContext {...renderProps} />
                </IntlProvider>
              </Provider>
            </MuiThemeProvider>,
          );

          res.setHeader('content-language', locale);
          res.render('index', {
            head,
            newrelicScript,
            body,
            locale,
            state: stringState,
            ...
          });
        };

        // Fetch components data and render HTML (no matter the fetch results
        // we need to render something)
        Promise.all(
          prefetchData(renderProps, store),
        )
        .then(render)
        .catch(prefetchError => next(prefetchError));
      } else {
        logger.warning('Ops !!! We should never arrive here :(');
        next();
      }
    });

And prefetch data:

export const prefetchData = (renderProps, store) => {
  const { components, params } = renderProps;
  const { dispatch } = store;
  const state = store.getState();
  const extractWrappedComponent = (component) => {
    if (component.WrappedComponent) {
      return extractWrappedComponent(component.WrappedComponent);
    }
    return component;
  };

  return components
    .filter(component => component !== undefined)
    // Get component, be aware if they are wrapped by redux connect or intl.
    .map(component => extractWrappedComponent(component))
    // Get the fetchData method of each component
    .map(component => component.fetchData)
    .filter(fetchData => fetchData !== undefined)
    // Invoke each fetchData to initialize the redux state.
    .map(fetchData => fetchData(state, dispatch, params));
};
2
  • Did you find solution? It's really stupid! I have a same problem. Commented Jul 3, 2017 at 19:55
  • @DarynK. I have answered my question with the solution. Hope it helps! Commented Jul 4, 2017 at 6:48

1 Answer 1

4

After looking into it, I found a solution that works for me. This is the helper (before was prefetchData). matchPath is from react-router-dom.

export const getRouteData = (routesArray, url, store) => {
  const { dispatch } = store;
  const state = store.getState();
  const extractWrappedComponent = (component) => {
    if (component.WrappedComponent) {
      return extractWrappedComponent(component.WrappedComponent);
    }
    return component;
  };

  return routesArray.map((route) => {
    const component = route.component;
    const extractedComponent = extractWrappedComponent(component);
    const match = matchPath(url, { path: route.path, exact: route.exact, strict: false });

    if (match) {
      if (extractedComponent.fetchData !== undefined) {
        extractedComponent.fetchData(state, dispatch, match.params);
      }
    }
  });
}

Then I call it:

getRouteData(routes, req.url, store);

Were routes is a static routes:

export const routes = [
  {
    path: '/',
    component: App
  },
  {
    path: '/:lang/chat',
    component: Chat
  }
  ...

My server file now does not have match. Instead:

if (context.url) {
  res.redirect(302, context.url);
} else {
  const render = () => {
    const body = renderApp(req, muiTheme, store, history, context);

    res.render('index', {
      head,
      body,
      locale,
      ...
    });
  };

  Promise.all(
    getRouteData(routes, req.url, store),
  )
  .then(render)
  .catch(prefetchError => next(prefetchError));

RenderApp file (Client has to be the same without StaticRouter):

const renderApp = (req, muiTheme, store, history, context) => {
  const app = (
    <MuiThemeProvider muiTheme={muiTheme}>
      <Provider store={store}>
        <IntlProvider>
          <StaticRouter
            location={req.url}
            context={context}
          >
            { appRoutes(history) }
          </StaticRouter>
        </IntlProvider>
      </Provider>
    </MuiThemeProvider>
  );

  return renderToString(app);
};

appRoutes file:

export default function appRoutes(history) {
  return (
    <ConnectedRouter history={history}>
      <div>
        {routes.map((route, key) => (
          <Route key={key} {...route} />
        ))}
      </div>
    </ConnectedRouter>
  );
}
Sign up to request clarification or add additional context in comments.

2 Comments

is there a way to call fetchdata from the subcomponents which are included in the main component?
I did not found any repo's doing tat, using redux-saga i can achieve it. But i did not find any way to parse the sub component and fetch the data.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.