3

I'm relatively new to SSR with React and Redux, so I assume I'm making a simple mistake that reflects my lack of understanding of the underlying frameworks. However, I'm about to put my head through a wall, so here goes. Hope someone can help.

If I try to navigate directly to the root page (/), I get the error Could not find "store" in the context of "Connect(Home)". In my case, App contains my shared navigation logic and global content, and (/) renders a Home component. If I navigate to a page that does NOT connect to Redux (e.g. Terms of Service), and THEN navigate to my home page, it renders perfectly. I assume this is some kind of server-side routing issue, but I've read everything I can find online, tried what feels like every possible permutation of state / store / context handling, and I can't resolve it.

Here is the skeleton of my code.

renderer.js

[...]

const modules = [];
const routerContext = {};

const bundle = (
    <Loadable.Capture report={m => modules.push(m)}>
        <ReduxProvider store={store}>
            <StaticRouter location={req.baseUrl} context={routerContext}>
                <CookiesProvider cookies={req.universalCookies}>
                    <App />
                </CookiesProvider>
            </StaticRouter>
        </ReduxProvider>
    </Loadable.Capture>
    );

const html = ReactDOMServer.renderToString(bundle);

if (routerContext.url) {
  redirect(301, routerContext.url);
}

// inject into HTML

[...]

configureStore.js

[...]

const createStoreWithMiddleware = compose(applyMiddleware(ReduxThunk))(createStore);

export default function configureStore(initialState = {}) {
    return createStoreWithMiddleware(rootReducer, initialState);
};

index.js (client)


const store = configureStore( window.__REDUX_STATE__ || {} );

const AppBundle = (
    <ReduxProvider store={store}>
        <BrowserRouter>
            <CookiesProvider>
                <App />
            </CookiesProvider>
        </BrowserRouter>
    </ReduxProvider>
);

window.onload = () => {
    Loadable.preloadReady().then(() => {
        ReactDOM.hydrate(
            AppBundle,
            document.getElementById('root')
        );
    });
};

app.js ("/" renders the Home component)

[...]

export default withCookies(App);

[...]

home.js

[...]

export default connect(mapStateToProps)(Home);

[...]
Rahul Jaswa
  • 509
  • 4
  • 17
  • Is this code available in some sort of public repo? If it is, could you please share the link? Thanks! – Josep Feb 20 '19 at 20:06
  • @Josep I've temporarily added the source code to a public repo: https://github.com/rahuljaswa/homehub-web. Thanks. – Rahul Jaswa Feb 25 '19 at 16:58

3 Answers3

2

I finally identified the problem. I was running an Express server to host server-rendered pages concurrently with the Node client. Unfortunately, the server was creating one instance of React, while the client was creating another instance of React. As a result, even though the code looked right, my Redux store wasn't being passed down the component hierarchy for the same React context.

Took me forever to debug and identify. This article finally pointed me in the right direction ... but it didn't get me all the way since the React debugger won't work on a server-rendered application.

There are multiple possible solutions to the problem. In my case, the simplest resolution was to consolidate all my dependencies across the client and the server, which implicitly removed all the redundant React instances.

Really hope this answer helps someone else because I spent more than a month trying to debug it once or twice a day a week.

Rahul Jaswa
  • 509
  • 4
  • 17
1

The store can be accessed from child components if you expose it through context or through props.
You could also expose it by adding the store to the window object, but that would not be a good practice because every visitor in your site can see and change the Store. (it can be useful if you don't care about safety of the redux store).
You could also use a setter, as suggested on this answer: Redux state value globally

The redux provider makes the store available to all of the children through context.

That Provider tag renders your parent component, lets call it the App component which in turn renders every other component inside the application.

Here is the key part, when we wrap a component with the connect() function, that connect() function expects to see some parent component within the hierarchy that has the Provider tag.

Invariant Violation: Could not find "store" in either the context or props of "Connect(SportsDatabase)"

So, according to the answer linked, your Home component must have a Provider or the store itself in it's context, that may be the cause of your error.

Eliâ Melfior
  • 359
  • 4
  • 19
  • I don't understand how this will fix the problem. Home is a subcomponent of App and App is wrapped by ReduxProvider in both the server and client bundles ... – Rahul Jaswa Feb 28 '19 at 11:26
  • Indeed, the ReduxProvider enables the store to all of the children components. But since the store can't be found inside your component, there is something wrong there. – Eliâ Melfior Feb 28 '19 at 12:17
0

On server side also you need to create your store whose state is taken as initial state for client side rendering. On client side, I can see that you have done this:

const store = configureStore( window.__REDUX_STATE__ || {} )  

Similar thing also needs to be done in the server side as well.

On server side, you need to do store = createStore(rootReducer) to create the store which is used here <ReduxProvider store={store}> in rendered.js.

Please revert for any doubts or clarifications.

Sunil Chaudhary
  • 4,481
  • 3
  • 22
  • 41