Cómo usar GraphQL en su aplicación Redux

Obtener y administrar datos en Redux requiere demasiado trabajo. Como señala Sashko Stubailo:

Desafortunadamente, los patrones para cargar datos del servidor de forma asincrónica en una aplicación Redux no están tan bien establecidos y, a menudo, implican el uso de bibliotecas auxiliares externas, como redux-saga. Debe escribir código personalizado para llamar a los puntos finales de su servidor, interpretar los datos, normalizarlos e insertarlos en la tienda, todo mientras realiza un seguimiento de varios estados de error y carga.

Al final de este tutorial, habrá aprendido cómo resolver este problema permitiendo que Apollo Client obtenga y administre los datos por usted. Ya no tendrá que escribir múltiples despachadores de acción, reductores y normalizadores para obtener y sincronizar datos entre su interfaz y su back-end.

Pero antes de comenzar el tutorial, asegúrese de que:

  • Conoce los conceptos básicos de las consultas GraphQL; si es completamente nuevo en GraphQL, debería volver después de realizar este tutorial.
  • Tiene algo de experiencia trabajando con React / Redux; de lo contrario, debe regresar después de hacer el tutorial de React y el tutorial de Redux.

En este tutorial, repasaremos 6 secciones juntos.

  1. Configuración del entorno del servidor (rápido)
  2. Configuración de la aplicación redux boilerplate
  3. Agregar cliente GraphQL (cliente Apollo)
  4. Obteniendo datos con la consulta GraphQL
  5. Obteniendo aún más datos
  6. Próximos pasos

1. Configuración del entorno del servidor

Primero, necesitamos un servidor GraphQL. La forma más fácil de tener un servidor en ejecución es completando este increíble tutorial.

Si se siente perezoso, puede simplemente clonar mi repositorio, que es casi el mismo servidor que obtendría si hiciera el tutorial usted mismo. El servidor admite consultas GraphQL para obtener datos de una base de datos SQLite.

Ejecútelo y veamos si funciona correctamente:

$ git clone //github.com/woniesong92/apollo-starter-kit$ cd apollo-starter-kit$ npm install$ npm start

El servidor debe ejecutarse en // localhost: 8080 / graphql. Navegue a esa página y vea si obtiene una interfaz GraphiQL que funcione con resultados como este:

GraphiQL le permite probar diferentes consultas y ver inmediatamente qué respuesta obtiene del servidor. Si no queremos el apellido de un autor y un mensaje de galleta de la fortuna en una respuesta, podemos actualizar la consulta como se muestra a continuación:

Y así es exactamente como nos gusta. Confirmamos que nuestro servidor está funcionando bien y devolviendo buenas respuestas, así que comencemos a construir el cliente.

2. Configuración de la aplicación de plantilla redux

Para simplificar, usaremos una plantilla redux para poder obtener toda la configuración (por ejemplo, Babel, webpack, CSS, etc.) de forma gratuita. Me gusta este texto estándar porque su configuración es fácil de seguir y es solo del lado del cliente, lo que la hace perfecta para este tutorial.

$ git clone //github.com/woniesong92/react-redux-starter-kit.git$ cd react-redux-starter-kit$ npm install$ npm start

Naveguemos a // localhost: 3000 / para ver si el servidor cliente se está ejecutando.

¡Hurra! El cliente se está ejecutando. Es hora de que comencemos a agregar un cliente GraphQL. Nuevamente, nuestro objetivo es obtener datos fácilmente del servidor y representarlos en la página de destino (HomeView) sin mucho esfuerzo mediante consultas GraphQL.

3. Agregar el cliente GraphQL (cliente Apollo)

Instale los paquetes apollo-client, react-apollo y graphql-tag.

$ npm install apollo-client react-apollo graphql-tag --save

Luego, abra el archivo src / containers / AppContainer.js, la raíz de nuestra aplicación Redux. Aquí es donde pasamos el almacén de redux a los componentes secundarios, utilizando el proveedor de react-redux.

import React, { PropTypes } from 'react'import { Router } from 'react-router'import { Provider } from 'react-redux'
class AppContainer extends React.Component { static propTypes = { history: PropTypes.object.isRequired, routes: PropTypes.object.isRequired, routerKey: PropTypes.number, store: PropTypes.object.isRequired }
render () { const { history, routes, routerKey, store } = this.props
return ( ) }}
export default AppContainer

Tenemos que inicializar un ApolloClient y reemplazar el Proveedor de react-redux con ApolloProvider de react-apollo.

import React, { Component, PropTypes } from 'react'import { Router } from 'react-router'import ApolloClient, { createNetworkInterface, addTypename } from 'apollo-client'import { ApolloProvider } from 'react-apollo'
const client = new ApolloClient({ networkInterface: createNetworkInterface('//localhost:8080/graphql'), queryTransformer: addTypename,})
class AppContainer extends Component { static propTypes = { history: PropTypes.object.isRequired, routes: PropTypes.object.isRequired, store: PropTypes.object.isRequired }
render () { const { history, routes } = this.props
return ( ) }}
export default AppContainer

¡Eso es! Acabamos de agregar un cliente GraphQL a una aplicación Redux simple con tanta facilidad.

Sigamos adelante y probemos nuestra primera consulta GraphQL.

4. Obtención de datos con consultas GraphQL

Abra src / views / HomeView.js

import React from 'react'import { connect } from 'react-redux'import { bindActionCreators } from 'redux'
export class HomeView extends React.Component { constructor(props) { super(props) }
render () { return ( 

Hello World

) }}
// This is where you usually retrieve the data stored in the redux store (e.g posts: state.posts.data)const mapStateToProps = (state, { params }) => ({
})
// This is where you usually bind dispatch to actions that are used to request data from the backend. You will call the dispatcher in componentDidMount.const mapDispatchToProps = (dispatch) => { const actions = {}
 return { actions: bindActionCreators(actions, dispatch) }}
export default connect( mapStateToProps, mapDispatchToProps)(HomeView)

HomeView es un contenedor Redux convencional (componente inteligente). Para usar consultas GraphQL en lugar de despachadores de acciones para obtener datos, haremos algunos cambios juntos.

  1. Elimine mapDispatchToProps () y mapStateToProps () por completo.
import React from 'react'import { connect } from 'react-redux'import { bindActionCreators } from 'redux'
export class HomeView extends React.Component { constructor(props) { super(props) }
 render () { return ( 

Hello World

) }}
export default connect({
})(HomeView)

2. Agregue mapQueriesToProps () y defina una consulta GraphQL que obtendrá la información del autor. Observe cómo esta es exactamente la misma consulta que probamos al principio usando la interfaz GraphIQL en el servidor.

import React from 'react'import { connect } from 'react-redux'import { bindActionCreators } from 'redux'
export class HomeView extends React.Component { constructor(props) { super(props) }
 render () { return ( 

Hello World

) }}
// NOTE: This will be automatically fired when the component is rendered, sending this exact GraphQL query to the backend.const mapQueriesToProps = ({ ownProps, state }) => { return { data: { query: gql` query { author(firstName:"Edmond", lastName: "Jones"){ firstName posts { title } } } ` } }}
export default connect({
})(HomeView)

3. Reemplace connect de react-redux con connect de react-apollo y pase mapQueriesToProps como argumento. Una vez que mapQueriesToProps está conectado a ApolloClient, la consulta obtendrá automáticamente los datos del backend cuando se procese HomeView y pasará los datos a través de accesorios.

import React from 'react'import { connect } from 'react-apollo' // NOTE: different connect!import gql from 'graphql-tag' // NOTE: lets us define GraphQL queries in a template language
export class HomeView extends React.Component { constructor(props) { super(props) }
render () { return ( 

Hello World

) }}
const mapQueriesToProps = ({ ownProps, state }) => { return { data: { query: gql` query { author(firstName:"Edmond", lastName: "Jones"){ firstName posts { title } } } ` } }}
export default connect({ mapQueriesToProps})(HomeView)

4. Renderice los datos que se transmiten desde los accesorios:

import React from 'react'import { connect } from 'react-apollo' // NOTE: different connect!import gql from 'graphql-tag' // NOTE: lets us define GraphQL queries in a template language
export class HomeView extends React.Component { constructor(props) { super(props) }
 render () { const author = this.props.data.author if (!author) { return 

Loading

}
 return ( 

{author.firstName}'s posts

{author.posts && author.posts.map((post, idx) => (
  • {post.title}
  • ))} ) }}
    const mapQueriesToProps = ({ ownProps, state }) => { return { data: { query: gql` query { author(firstName:"Edmond", lastName: "Jones"){ firstName posts { title } } } ` } }}
    export default connect({ mapQueriesToProps})(HomeView)

    If all went well, your rendered HomeView should look like below:

    To fetch and render the data we wanted, we didn’t have to write any action dispatcher, reducer, or normalizer. All we had to do on the client was to write a single GraphQL query!

    We successfully achieved our initial goal. But that query was quite simple. What if we wanted to display all authors instead of just one author?

    5. Fetching even more data

    In order to fetch and display all authors, we have to update our GraphQL query and render method:

    import React from 'react'import { connect } from 'react-apollo' // NOTE: different connect!import gql from 'graphql-tag' // NOTE: lets us define GraphQL queries in a template language
    export class HomeView extends React.Component { constructor(props) { super(props) }
    render () { const authors = this.props.data.authors if (!authors) { return 

    Loading

    }
     return ( {authors.map((author, idx) => ( 

    {author.firstName}'s posts

    {author.posts && author.posts.map((post, idx) => (
  • {post.title}
  • ))} ))} ) }}
    const mapQueriesToProps = ({ ownProps, state }) => { return { data: { query: gql` query { authors { firstName posts { title } } } ` } }}
    export default connect({ mapQueriesToProps})(HomeView)

    However, once you refresh your browser HomeView page, you will notice that you have an error in your console:

    ApolloError {graphQLErrors: Array[1], networkError: undefined, message: “GraphQL error: Cannot query field “authors” on type “Query”. Did you mean “author”?”}

    Ah, right! In our GraphQL server, we didn’t really define how to fetch authors.

    Let’s go back to our server and see what we have. Open the file apollo-starter-kit/data/resolvers.js

    import { Author, FortuneCookie } from './connectors';
    const resolvers = { Query: { author(_, args) { return Author.find({ where: args }); }, getFortuneCookie() { return FortuneCookie.getOne() } }, Author: { posts(author) { return author.getPosts(); }, }, Post: { author(post) { return post.getAuthor(); }, },};
    export default resolvers;

    Looking at Query resolver, we notice that our GraphQL server only understands author and getFortuneCookie queries now. We should teach it how to “resolve” the query authors.

    import { Author, FortuneCookie } from './connectors';
    const resolvers = { Query: { author(_, args) { return Author.find({ where: args }); }, getFortuneCookie() { return FortuneCookie.getOne() }, authors() { // the query "authors" means returning all authors! return Author.findAll({}) } }, ...};
    export default resolvers;

    We are not done yet. Open the file apollo-starter-kit/data/schema.js

    const typeDefinitions = `...
    type Query { author(firstName: String, lastName: String): Author getFortuneCookie: String}schema { query: Query}`;
    export default [typeDefinitions];

    This Schema makes it clear what kind of queries the server should expect. It doesn’t expect authors query yet so let’s update it.

    const typeDefinitions = `...
    type Query { author(firstName: String, lastName: String): Author getFortuneCookie: String, authors: [Author] // 'authors' query should return an array of // Author}schema { query: Query}`;
    export default [typeDefinitions];

    Now that our GraphQL server knows what the “authors” query means, let’s go back to our client. We already updated our query so we don’t have to touch anything.

    export class HomeView extends React.Component {
    ...
    const mapQueriesToProps = ({ ownProps, state }) => { return { data: { query: gql` query { authors { firstName posts { title } } } ` } }}
    export default connect({ mapQueriesToProps})(HomeView)

    With this query we expect to get all authors with their first names and posts. Go ahead and refresh the browser to see if we are getting the right data.

    If everything went well, your HomeView page will look like above.

    6. Next steps

    This tutorial only explores a small part of GraphQL and leaves out a lot of concepts such as updating data on the server or using a different backend server (e.g. Rails).

    While I work to introduce these in subsequent tutorials, you can read Sashko’s post or the Apollo Client Doc to better understand what’s going on under the hood (for example, what happened when we replaced Provider with ApolloProvider?).

    Digging into the source code of GitHunt, a full-stack Apollo Client and Server example app, also seems a great way to learn.

    If you have feedback, please leave it in the comment. I will try my best to be helpful :)