supasate / connected-react-router

A Redux binding for React Router v4
MIT License
4.73k stars 593 forks source link

Server-side rendering doesn't work #212

Open zdila opened 5 years ago

zdila commented 5 years ago

When using SSR with ReactDOMServer.renderToString I get following exception:

    Invariant Violation: Could not find "store" in the context of "Connect(ConnectedRouterWithContext)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(ConnectedRouterWithContext) in connect options.
        at invariant (/home/user/app/node_modules/invariant/invariant.js:40:15)
        at Connect.renderWrappedComponent (/home/user/app/node_modules/react-redux/lib/components/connectAdvanced.js:162:32)
        at ReactDOMServerRenderer.render (/home/user/app/dist/node_modules/react-dom/cjs/react-dom-server.node.development.js:2494:55)
        at ReactDOMServerRenderer.read (/home/user/app/dist/node_modules/react-dom/cjs/react-dom-server.node.development.js:2357:19)
        at Object.renderToString (/home/user/app/dist/node_modules/react-dom/cjs/react-dom-server.node.development.js:2729:25)
        at renderToString (/home/user/app/dist/server/webpack:/src/server/indexServer.js:220:18)

Dependencies:

    "connected-react-router": "6.0.0",
    "react": "16.7.0",
    "react-dom": "16.7.0",
    "react-redux": "6.0.0",
    "react-router-dom": "4.3.1",
    "redux": "4.0.1",

Compoents:

    <Provider store={store}>
      <ErrorCatcher>
        <ConnectedRouter history={history}>
          <Switch>
            <Route path="/recorder/" component={Recorder} />
            <Route path="/" component={Regular} />
          </Switch>
        </ConnectedRouter>
      </ErrorCatcher>
    </Provider>

Rendering client-side only works.

zdila commented 5 years ago

Minimal testcase:

import ReactDOMServer from 'react-dom/server';
import React from 'react';
import { Provider } from 'react-redux';
import { ConnectedRouter, push } from 'connected-react-router';
import { createStore } from 'redux';
import createHistory from 'history/createMemoryHistory';

const history = createHistory();
const store = createStore(() => {});

console.log('DUMP', ReactDOMServer.renderToString((
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <div>Hello</div>
    </ConnectedRouter>
  </Provider>
)));
kevindice commented 5 years ago

You may want to use <StaticRouter> on the server side. Here's an example from a repo that adds SSR to a non-ejected CRA starter app. https://github.com/cereallarceny/cra-ssr/blob/master/server/loader.js

kvedantmahajan commented 5 years ago

I'm facing the same issue without SSR.

cristian-sima commented 5 years ago

Mee too

rodrigobacelli commented 5 years ago

Me too, without SSR

neroneroffy commented 5 years ago

same issue when using ssr

kmcrawford commented 5 years ago

I'm using the connected-react-router v6 and still having the issue using SSR.

lorenjohnson commented 5 years ago

Just moved off of react-router to connected-react-router v6.(.3..2). We have an SSR setup and are running into the same issue.

archiewx commented 5 years ago

When using SSR with ReactDOMServer.renderToString I get following exception:

    Invariant Violation: Could not find "store" in the context of "Connect(ConnectedRouterWithContext)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(ConnectedRouterWithContext) in connect options.
        at invariant (/home/user/app/node_modules/invariant/invariant.js:40:15)
        at Connect.renderWrappedComponent (/home/user/app/node_modules/react-redux/lib/components/connectAdvanced.js:162:32)
        at ReactDOMServerRenderer.render (/home/user/app/dist/node_modules/react-dom/cjs/react-dom-server.node.development.js:2494:55)
        at ReactDOMServerRenderer.read (/home/user/app/dist/node_modules/react-dom/cjs/react-dom-server.node.development.js:2357:19)
        at Object.renderToString (/home/user/app/dist/node_modules/react-dom/cjs/react-dom-server.node.development.js:2729:25)
        at renderToString (/home/user/app/dist/server/webpack:/src/server/indexServer.js:220:18)

Dependencies:

    "connected-react-router": "6.0.0",
    "react": "16.7.0",
    "react-dom": "16.7.0",
    "react-redux": "6.0.0",
    "react-router-dom": "4.3.1",
    "redux": "4.0.1",

Compoents:

    <Provider store={store}>
      <ErrorCatcher>
        <ConnectedRouter history={history}>
          <Switch>
            <Route path="/recorder/" component={Recorder} />
            <Route path="/" component={Regular} />
          </Switch>
        </ConnectedRouter>
      </ErrorCatcher>
    </Provider>

Rendering client-side only works.

@zdila My solution is following:

In the store:

export const getStoreConfiguration = (preloadedState) => {
  const middlewares = [];

  let history;
  let reducers = {
    init(state, action) {
      return { ...state, ...action.payload };
    },
  };

  if (isClient()) {
    history = createBrowserHistory();
    reducers.router = connectRouter(history);
    middlewares.push(routerMiddleware(history));
  }

  return {
    store: createStore(
      combineReducers(reducers),
      preloadedState,
      composeEnhancer(applyMiddleware(...middlewares))
    ),
    history,
  };
};

isClient

export default () => {
  try {
    if (window && window.document && window.document.createElement) return true;

    return false;
  } catch (err) {
    return false;
  }
}

in the client

const { store, history } = getStoreConfiguration(preloadedState);
<Provider store={store}>
    <ConnectedRouter history={history}>
      <ClientApp />
    </ConnectedRouter>
  </Provider>,

The key is check the client or server, for difference env to create the store and history.

zdila commented 5 years ago

@zsirfs I am using solution provided in https://github.com/supasate/connected-react-router/issues/212#issuecomment-449390810. But still old approach used to work in older versions of connected-react-router (react-router-redux).

archiewx commented 5 years ago

@zsirfs I am using solution provided in #212 (comment). But still old approach used to work in older versions of connected-react-router (react-router-redux).

Ok, I am using my solution work well.

Sawtaytoes commented 5 years ago

I have an older project from 2015, but got it working. Newer projects would probably work the same.

I had to use StaticRouter server-side 👎, but it worked when I made connectedRouter optional in combineReducers:

const createRootReducer = history => (
  combineReducers({
    collapsibleMenu: collapsibleMenuReducer,
    pageMeta: pageMetaReducer,
    playback: playbackReducer,
    ...(
      history
      && { router: connectRouter(history) }
    ),
    showSongs: showSongsReducer,
  })
)

Then, in my server renderer, I did this:

const context = {}
const store = compose()(createStore)(createRootReducer())

const renderedContent = renderToString(
  <Provider store={store}>
    <StaticRouter
      context={context}
      location={req.url}
    >
      <Pages />
    </StaticRouter>
  </Provider>
)

const renderedPage = renderSite(renderedContent, store.getState())

if (context.url) {
  console.log('redirect');
  res.writeHead(302, { Location: context.url })
  res.end()
}
else {
  console.log(req.url);
  res.send(renderedPage).end()
}