Stretch0
Stretch0

Reputation: 9283

Map query to props with Apollo.js in React

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 })

Upvotes: 1

Views: 2452

Answers (1)

Daniel Rearden
Daniel Rearden

Reputation: 84867

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
    }
  }`

Upvotes: 3

Related Questions