1

I have just set up graphql on my server and wanting to connect Apollo to my frontend.

I was able to integrate Apollo with Redux (How to pass initial state to reducer) but now want to map state to props via redux connect.

My current Home component looks like this:

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { graphql } from 'react-apollo';

import './Home.css'

class Home extends Component {

  render() {
    return (
      <section className='home-container'>
        <h1>Site Name</h1>
      </section>
    )
  }
}

const mapStateToProps = state => ({
    curPage: state.curPage
})

export default connect(
  mapStateToProps,
)(Home)

I essentially want to replace mapStateToProps with mapQueryToProps however I am unsure how this works.

Does this make a request to my /graphql endpoint and map the response to curPage?

Will this happen before the first render? As in will this.props.curPage be available within componentWillMount()? (I need this for seo purposes to make sure all the content is rendered server side).

Where do I configure my graphql endpoint? I see some examples configuring it within their store. My store is split into store.js and index.js slightly but is as follows:

// index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';

import App from './containers/App/App';

import initialState from './initialState';
import configureStore from './store';

import { ApolloClient, ApolloProvider } from 'react-apollo';

const client = new ApolloClient();

// Let the reducers handle initial state

initState() // eslint-disable-line

// if we are in production mode, we get the initial state from the window object, otherwise, when we are in dev env we get it from a static file
const preloadedState = window.__INITIAL_STATE__ === '{{__PRELOADEDSTATE__}}' ? initialState : window.__INITIAL_STATE__

const store = configureStore(preloadedState)

ReactDOM.render(
  <ApolloProvider store={store} client={client}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </ApolloProvider>,
  document.getElementById('root')
)

// store.js

import { createStore, applyMiddleware, compose } from 'redux'
import { createLogger } from 'redux-logger'
import reducers from './reducers'
import { ApolloClient } from 'react-apollo';

const logger = createLogger()
const client = new ApolloClient();

export default function configureStore(initialState = {}) {

  // Create the store with two middlewares
  const middlewares = [
  //  sagaMiddleware
    logger,
    client.middleware()
  ]

  const enhancers = [
    applyMiddleware(...middlewares)
  ]

  const store = createStore(
    reducers,
    initialState,
    compose(...enhancers)
  )

  // Extensions
  store.asyncReducers = {} // Async reducer registry

  return store
}

Update

I am now importing my client from my store so that I only have one instance of my client. I am also using /graphql as my endpoint so I shouldn't need to configure the endpoint.

// index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';

import App from './containers/App/App';

import initialState from './initialState';
import {configureStore, client} from './store';

import { ApolloClient, ApolloProvider } from 'react-apollo';

initState() // eslint-disable-line

// if we are in production mode, we get the initial state from the window object, otherwise, when we are in dev env we get it from a static file
const preloadedState = window.__INITIAL_STATE__ === '{{__PRELOADEDSTATE__}}' ? initialState : window.__INITIAL_STATE__

const store = configureStore(preloadedState)

ReactDOM.render(
  <ApolloProvider store={store} client={client}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </ApolloProvider>,
  document.getElementById('root')
)

Still a bit confused about graphql(MY_QUERY, { props: mapQueryToProps })(Home)

I have a query to get curPage which works in graphiql: (I'm guessing MY_QUERY should be equal to this?)

query {
  findResource(filter: {resource: "pages", slug: "about"}) {
    id
    title
    slug
    path
    sections
  }
}

and returns:

{
  "data": {
    "findResource": [
      {
        "id": "5",
        "title": "About",
        "slug": "about",
        "path": "/about",
        "sections": [
          {
            "type": "masthead",
            "mh_title": "",
            "video": false,
            "image": [],
            "showCta": false,
            "cta": []
          },
          {
            "type": "text_image",
            "alignment": "left",
            "text_image": [
              {
                "type": "text",
                "title": "",
                "content": "",
                "call_to_action": false,
                "cta": []
              }
            ]
          }
        ]
      }
    ]
  }
}

Would that mean my

const mapQueryToProps = ({data: { getPage: { page } }, ownProps}) => ({ curPage: page }) 

should look like:

const mapQueryToProps = ({data: { findResource: { page } }, ownProps}) => ({ curPage: page })

1 Answer 1

3

Couple of points:

You're creating two instances of ApolloClient -- one in index.js and one in store.js -- you could pass the client to configureStore and that way you're utilizing the same instance.

If your graphQL endpoint is anywhere other than /graphql, you'll have to utilize a custom network interface. Example from the docs:

import { ApolloClient, createNetworkInterface } from 'react-apollo';
const networkInterface = createNetworkInterface({
  uri: 'http://api.example.com/graphql'
});
const client = new ApolloClient({ networkInterface });

With just the minimal setup, the data from your query will not be available immediately -- the component will mount and rerender once the data is available. However, the documentation provides a couple of ways to get around that.

As far as how the query data gets mapped to props, it's important to note that Apollo does not utilize connect, but rather has its own HOC. You'll use it in a similar manner:

graphql(MY_QUERY, { props: mapQueryToProps })(Home)

The function you assign to props will be passed a single object that has two properties, data and ownProps. Data includes the result of your query and ownProps refers to any existing props on the component. So your mapQueryToProps might look something like:

const mapQueryToProps = ({data: { getPage: { page } }, ownProps}) =>
  ({ curPage: page })

You can also omit specifying props altogether, and you'll just get the whole data object as a prop. The above is just a way of fine-tuning what actually gets assigned as props to your component.

If there are still parts of your state you want to keep using redux for, there's a section in the docs for integrating redux and Apollo. It's pretty clean if you use compose or recompose.

Apollo's documentation for react is pretty clear and to-the-point. If you haven't already, you may want to just go through the Setup and options and Usage sections, and maybe even through any applicable Recipes before proceeding any further.

EDIT: The query you pass in to the HOC will need to be a string. The easiest thing to do is to import and use the gql tag. Based on the query you provided, it would look like this:

const MY_QUERY = gql`
  query {
    findResource(filter: {resource: "pages", slug: "about"}) {
      id
      title
      slug
      path
      sections
    }
  }`
Sign up to request clarification or add additional context in comments.

4 Comments

Ok so I have made the changes to the client as per your recommendation. I have updated my original post with the way I have done this. Secondly I am still a little unsure about how to mapQueryToProps. I have updated my original post with another example. Am I on the right track? I have read the documentation you suggested as well.
See my edit for how to pass in the query. You can read more here. As far as the mapQueryToProps... In my exampe, I was just guessing at what your data structure would be; look at what the query's returning -- that's the data object you're working with in mapQueryToProps. I'm guessing you need the first object inside the array that's returned so you could just do something like: ({data}) => ({curPage: data.findResource[0]}).
Or don't worry about setting the props for a simple case like this. Just do graphql(MY_QUERY)(Home) and then you can access the data inside your component with props.data. Might be less of a headache.
any idea why I would be getting Invariant Violation: Could not find "client" in the context of "Apollo(Home)". Wrap the root component in an <ApolloProvider>? As you can see in my index.js I am wrapping my App in <ApolloProvider>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.