Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

SSR error "cannot read property 'store' of undefined" #155

Closed
rewop opened this issue Aug 19, 2016 · 9 comments · Fixed by #165
Closed

SSR error "cannot read property 'store' of undefined" #155

rewop opened this issue Aug 19, 2016 · 9 comments · Fixed by #165
Assignees
Labels

Comments

@rewop
Copy link

rewop commented Aug 19, 2016

Hi,

I am using react-apollo to perform server side rendering and I am receiving the error

TypeError: Cannot read property 'store' of undefined
   at new GraphQL (graphql.tsx:255:14)
   at getQueriesFromTree (server.ts:48:11)
   at getDataFromTree (server.ts:82:3)
   at server.ts:89:60
   at Array.map (native)
   at server.ts:89:46
   ...

This error is only present server side, and not client side. When I disable SSR client side works perfectly.

I looked into the code and I found out that the error is thrown from the function getDataFromTree in the server.ts file.

Basically when the function getQueriesFromTree tries to get the queries from the tree, when encounter the GraphQL component it throws the error.

I see that the GraphQL component is created by the decorator graphql that handles the call to the serevr. It looks like that when the component is initialised, it cannot find the client neither from the context nor from the props (despite I have the root wrapped in ApolloProvider).

Also I found another small bug. When this kind of situation arises, a warning should be shown, instead of a uncaught exception. The problem is that this line should be after the invariant check against the client prop in the line soon after.

I haven't managed to find out yet why the client is not in the context. The following is how my application is set up.

In my application I am using react-router, redux and react-apollo. My root component looks like this:

const Root = ({
    history: _history,
    routes,
    routerState,
    client,
    store
}) => {
    let component;
    if (__SERVER__)
        component = <RouterContext {...routerState} />;
    else
        component = <Router history={_history} routes={routes} render={scrollHandlingMiddleware} />;

    return (
        <ApolloProvider client={client} store={store}>
            {component}
        </ApolloProvider>
    );
};

My client is created with this function, and passed to the Root component:

import ApolloClient, { createNetworkInterface } from "apollo-client";

export default () => {
    const networkInterface = createNetworkInterface(process.env.GRAPH_URL);
    return new ApolloClient({
        networkInterface,
        ssrMode: __SERVER__
    });
};

The only container I am using with react-apollo at the moment looks like this:

import React, { PropTypes } from "react";
import MyComponent from "../components/MyComponent";
import { graphql } from "react-apollo";
import gql from "graphql-tag";

const MY_QUERY = gql`
     ...
`;

const dataToProps = ({ data }) => {
    return {
        ...
    };
};

const withData = graphql(MY_QUERY, {
    props: dataToProps
});

const MyContainer = ({ error, loading, ...rest }) => {
    if (error) return <span />;
    if (loading) return <h2>Loading</h2>;
    return <MyComponent ...rest />;
};

MyContainer.propTypes = {
    loading: PropTypes.bool,
    error: PropTypes.bool,
    ...
};

export default withData(MyContainer);

Any idea what the problem could be? Am I doing something wrong?

I am willing to contribute on this one if we find the problem.

@rewop
Copy link
Author

rewop commented Aug 19, 2016

I did some digging, and I have might found the problem.

I saw that GraphQL was instantiated twice, despite I use graphql decorator only once. The first time the context is set correctly set, while the second time it is an empty object.

Looking at the function getDataFromTree I see it first calls getQueriesFromTree to run the queries and to get their results, and then, it runs itself again with the result of the queries. I guess the second call is to calculate the final state to rehydrate the client. Forgive me if what I just said is wrong, but getDataFromTree and getQueriesFromTree are quite difficult functions to track.

Now the second call of getDataFromTree uses as context the result returned by the getQueriesFromTree. Looking at the implementation of getQueriesFromTree, I see that the final result returned is the result of the root component of my app (the one passed to React.render function) . In my case that component is an instance of Root as you can see in my comment above.

That component has no context set which means the result of getQueriesFromTree is an empty object that then is given to the next call of getDataFromTree. The component passed there though is not the root, but it is the GraphQL instance. This makes my app fail because GraphQL has no context at this point.

Is it possible that getDataFromTree expects ApolloProvider as root component? And if so am I correct it is not documented?

My fix is to change the Root component above to not render ApolloProvider and in my server and client I now render the Root component wrapped in ApolloProvider.

Root.js:

const Root = ({
    history: _history,
    routes,
    routerState,
    client,
    store
}) => {
    let component;
    if (__SERVER__)
        component = <RouterContext {...routerState} />;
    else
        component = <Router history={_history} routes={routes} render={scrollHandlingMiddleware} />;
    return (component);
};

The function that renders server side:

    const Root = require("../client/containers/Root").default;
    const client = createClient();
    const store = createStore({ client });
    const routes = getRoutes();

    match({ routes, location: req.originalUrl }, (err, redirectLocation, routerState) => {
        if (err) return next(err);
        else if (redirectLocation)
            return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
        else if (!routerState)
            return res.status(404).send("Not Found");

        const component = (
              <ApolloProvider client={client} store={store}>
                   <Root routerState={routerState} routes={routes} />
              </ApolloProvider>
        ), rootContainer);

        return renderToStringWithData(component)
        .then(markup => {
            hydrateOnClient({
                ...htmlRenderProps,
                markup
            });
        })
        .catch(err => {
            console.error("Error server side rendering", err);
            return next(err);
        });
    });

This fixed my app.

In my opinion, this behavior should be improved. It is common in React community to use a Root component that renders all the providers. And it should not be a worry which provider should be rendered as root.

I don't know the rationale behind getDataFromTree implementation, but I believe there is great room of improvement. And I am willing to contribute, if I can get help on this one.

@jbaxleyiii
Copy link
Contributor

@rewop thank you so much for such excellent debugging! It shouldn't be required that the parent container is the ApolloProvider. I'll happily work on fixing this unless you would like to take a try at it?

@jbaxleyiii jbaxleyiii added the bug label Aug 22, 2016
@jbaxleyiii jbaxleyiii self-assigned this Aug 22, 2016
@rewop
Copy link
Author

rewop commented Aug 22, 2016

@jbaxleyiii I could give it a try for sure. But I read that a fix could be already on its way from #157. If not I am happy to contribute.

@rewop
Copy link
Author

rewop commented Aug 22, 2016

@jbaxleyiii I think I overlooked a bit the description, and I see now that #157 is a different problem. I will give a first go to this later on as I have some time.

@jbaxleyiii
Copy link
Contributor

This should be fixed in the next version (set to release today)

If it is not, please reopen!

@rewop
Copy link
Author

rewop commented Aug 23, 2016

@jbaxleyiii Great! I am also working on a SSR demonstration with react-apollo based on a fork from https://github.com/apollostack/GitHunt.

@stubailo
Copy link
Contributor

@rewop heads up - GitHunt has SSR now

@rewop
Copy link
Author

rewop commented Aug 23, 2016

Perfect then. I missed the open PR yesterday when I forked.

@stubailo
Copy link
Contributor

Also, another heads up, I just split up the API server and the React frontend into separate repos, since we want to share the server between all of the example frontends.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants