React-apollo: Delaying getting data until after initial render

Created on 31 Jan 2017  路  5Comments  路  Source: apollographql/react-apollo

Currently, it can take a few seconds to load large amounts of data from the cache. I know this is actively being worked on in the apollo-client project, but in the mean time I would like to do the following: First load the component without any data in order to display a loading indicator, then initiate the query. Once the query finishes, the component would render as usual.

Looking at how react-apollo is built, the child component is not rendered until the initial query is set up which includes hitting the cache which in some cases could take a few seconds resulting in a hanging UI.

Is it currently possible to delay queries like this with react-apollo? Or would I have to use withApollo to run the query with the client myself?

Thanks!

Most helpful comment

I suggest you create a wrapper component (which could be in the form of an HOC) to add this functionality 馃槉

Such an HOC would look like the following:

function loadingComponentFirst(WrappedComponent) {
  return class LoadingComponentFirst extends Component {
    state = {
      renderWrappedComponent: false,
    };

    componentDidMount() {
      setTimeout(() => {
        this.setState({
          renderWrappedComponent: true,
        });
      }, 0);
    }

    render() {
      const { renderWrappedComponent } = this.state;
      return renderWrappedComponent ? <WrappedComponent {...this.props} /> : <Loading />;
    }
  }
}

Let me know if this works for you. If it doesn鈥檛 we can re-open 馃憤. The setTimeout shouldn鈥檛 be necessary, but I know some React tools will warn if you call setState in componentDidMount. In this instance you have a valid use case.

May I also ask how much data you are loading @bradabelgit and @StefanWegener? It鈥檚 an incredible shame that reading from the cache takes seconds. It would be helpful to know what the data load of users is so we can optimize for that number.

All 5 comments

I have the same issue

I suggest you create a wrapper component (which could be in the form of an HOC) to add this functionality 馃槉

Such an HOC would look like the following:

function loadingComponentFirst(WrappedComponent) {
  return class LoadingComponentFirst extends Component {
    state = {
      renderWrappedComponent: false,
    };

    componentDidMount() {
      setTimeout(() => {
        this.setState({
          renderWrappedComponent: true,
        });
      }, 0);
    }

    render() {
      const { renderWrappedComponent } = this.state;
      return renderWrappedComponent ? <WrappedComponent {...this.props} /> : <Loading />;
    }
  }
}

Let me know if this works for you. If it doesn鈥檛 we can re-open 馃憤. The setTimeout shouldn鈥檛 be necessary, but I know some React tools will warn if you call setState in componentDidMount. In this instance you have a valid use case.

May I also ask how much data you are loading @bradabelgit and @StefanWegener? It鈥檚 an incredible shame that reading from the cache takes seconds. It would be helpful to know what the data load of users is so we can optimize for that number.

@calebmer That could work, in the meantime I pulled out the cache completely, and am simply using Apollo for graphql queries which then load up my own self-managed redux store. The control and simplicity is trumping the automation that apollo offers for the moment at least.

I was trying to load about 5K objects before things started taking seconds.

@calebmer Thanks greatly for your example approach.

I've been stubbornly trying to implement my app in such a way that all of my components will define everything they care about in their own state, and then basically sync that with what's in Apollo's cache. The idea is that on component load, it checks the Apollo local cache to see if a copy of its state is there. If so, it's state gets set to that. If not, it then it gets its state from a pre-defined object, and then writes that state to the Apollo cache. When the state updates, it updates in both its own state and the cache.

I like this approach because it allows me to write components that can exist on their own outside of Apollo, but if using Apollo, they are extensible.

Unfortunately this has been much more difficult than it sounds. I tried your approach, but am stuck now at the error TypeError: children is not a function. Not sure why; equivalent queries that I'm using elsewhere seem to work fine.

Here's a shortened version of the JavaScript module with the component class in question. I've cleaned out a lot of extra methods and properties that I don't think are relevant to the issue. You can see that I've also tried a very simple graphql query (the query resolves normally in other components that don't use this HOC approach), but still get the same error.

import React, {Component} from "react";
import { graphql, compose, Query } from 'react-apollo';
import gql from 'graphql-tag';
import GettingStartedTemplate from './GsBase';


class SelectWidgetPageBase extends Component {
  state = this.props.state;

  render() {
    // ...What you see below is the result of a call to a function of a libarary I'm working on; basically equivalent to jsonToGraphQLQuery(this.state).
    // const gqlQuery = gql`query @client { appState { SocialCircleState { options { myself { text id icon } lovedOne { text id icon } family { text id icon } } } } }`;
    const gqlQuery = gql`{ appState @client }`;

    return (
      <Query query={gqlQuery}>
        {({ loading, error, data }) => {
          if (loading) return "Loading...";
          if (error) return `Error! ${error.message}`;
          return (
            <GettingStartedTemplate cachedstate={data}/>
          );
        }}
      </Query>
    );
  }
}


const updateAppState = gql`
  mutation ($key: String!, $val: Object) {
    updateAppState(key: $key, val: $val) @client
  }
`;
// ...For reference, here is a snippet of the relevant mutation.
//  const mutations = {
//    Mutation: {
//     updateAppState: (_, { key, val }, { cache }) => {
//       let appState = {
//           __typename: 'AppState',
//         };
//       appState[key] = val;
//       cache.writeData({data: {appState}});
//       return null;
//     }
//   }
// };

const SWPB2 = compose(  // SWPB2 = SelectWidgetPageBase2
  graphql(updateAppState),
)(SelectWidgetPageBase);

function loadingComponentFirst(SelectWidgetPageBase) {
  class LoadingComponentFirst extends Component {
    state = {};

    componentDidMount() {
      this.props.mutate({
        variables: { key: this.props.state.__typename, val: this.props.state}
      });
      setTimeout(() => {
        this.state = this.props.state;
      }, 0);
    }

    render() {
      return this.state !== {} ? <SelectWidgetPageBase {...this.props} /> : <p>Loading...</p>;
    }
  }
  return compose(graphql(updateAppState))(LoadingComponentFirst);
}

export default compose(graphql(updateAppState))(loadingComponentFirst(SWPB2));

And here's the actual code: click me

Kind of sad, but I've been struggling with Apollo for a couple months now; just coming back to this project whenever I have the time. I've even been contributing to "json-to-graphql-query" as a means of streamlining the functionality to my use case.

@joeflack4 Any chance you can put together a small runnable reproduction that demonstrates the children is not a function error? This can happen when there is a problem with the <Query /> component render prop, but in the quick example you've provided the render prop looks okay. A reproduction would definitely help us troubleshoot this further. Thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bdouram picture bdouram  路  3Comments

notadamking picture notadamking  路  3Comments

Cedcn picture Cedcn  路  3Comments

reggi picture reggi  路  3Comments

ARMGAMES picture ARMGAMES  路  3Comments