JamesG
JamesG

Reputation: 2018

Apollo Client and Redux setup causes infinite render loop

I'm trying to link up React Apollo with Redux so Apollo performs the queries and mutations, and the returned data is dispatched to the Redux store in order to distribute the data around the app.

I believe I'm close to getting it right, but for some reason the app goes into an infinite loop of Redux dispatches, and I can't figure out why.

See code below:

class Admin extends Component {
      constructor(props) {
        super(props);
      }

      render({
        adminAllTokens
      }, {}) {
        return ( /* JSX */  )
      );
    }
    }

    const AllRefreshTokens = gql `
      query {
        allUsers {
          refreshToken
          email
        }
      }
    `;

    const gqlWrapper = graphql(AllRefreshTokens, {
      props: ({
        ownProps,
        data
      }) => {
        ownProps.receivedAdminTokens(data.allUsers); //dispatch to Redux store
        return {
          ...data,
          gqladminAllTokens
        };
      }
    });

    function mapStateToProps(state, ownProps) {
      return {
        adminAllTokens: state.auth.adminAllTokens
      };
    }

    function mapDispatchToProps(dispatch) {
      return {
        receivedAdminTokens: tokens => {
          dispatch(adminTokensReceived(tokens));
        }
      };
    }

    const reduxWrapper = connect(mapStateToProps, mapDispatchToProps);
    export default compose(reduxWrapper, gqlWrapper)(Admin);

The adminTokensReceived() action is in the reducer file:

export const adminTokensReceived = tokens => ({
    type: 'ADMIN_TOKENS_RECEIVED',
    tokens
});

The GraphQL query only sends one network request, but the console is showing the ADMIN_TOKENS_RECEIVED action dispatching constantly and crashes the browser.

Thanks in advance

Upvotes: 1

Views: 1350

Answers (1)

Daniel Rearden
Daniel Rearden

Reputation: 84687

Whenever the Apollo HOC receives new props, it causes your action to fire, which updates the store and sends new props to your Apollo HOC, which causes your action to fire...

There's a couple of different ways you could handle this. In my mind, the most straightforward would be to drop the graphql HOC and use withApollo instead. Something like:

compose(
  withApollo,
  connect(mapStateToProps, mapDispatchToProps)
  lifecycle({
    componentDidMount() {
      const { client } = this.props
      client.query({ query: AllRefreshTokens })
        .then(({data}) => {
          receivedAdminTokens(data.allUsers)
        })
        .catch( //any error handling logic )
    }
  })
)

The above uses recompose's lifecycle but you could just as easily stick the componentDidMount method inside your component.

That said, it seems a little redundant to use Redux to store the results of your GraphQL queries when Apollo already does it for you.

Apollo's default behavior is to retrieve the data from the cache first, and only make a network request if the data doesn't exist (which is also why you only saw the one network call). That means any number of components inside your app could be wrapped with the same graphql HOC, and only the first component to be rendered would trigger a request to your GraphQL endpoint -- all other components would get their data from the cache.

Upvotes: 1

Related Questions