Comment utiliser GraphQL dans votre application Redux

La récupération et la gestion des données dans Redux nécessitent trop de travail. Comme le souligne Sashko Stubailo:

Malheureusement, les modèles de chargement asynchrone des données de serveur dans une application Redux ne sont pas aussi bien établis et impliquent souvent l'utilisation de bibliothèques d'assistance externes, comme redux-saga. Vous devez écrire du code personnalisé pour appeler les points de terminaison de votre serveur, interpréter les données, les normaliser et les insérer dans le magasin, tout en gardant une trace des divers états d'erreur et de chargement.

À la fin de ce didacticiel, vous aurez appris à résoudre ce problème en laissant le client Apollo récupérer et gérer les données pour vous. Vous n'aurez plus à écrire plusieurs répartiteurs d'action, réducteurs et normalisateurs pour récupérer et synchroniser les données entre votre front-end et votre back-end.

Mais avant de commencer le didacticiel, assurez-vous que:

  • Vous connaissez les bases des requêtes GraphQL - si vous êtes entièrement nouveau dans GraphQL, vous devriez revenir après avoir fait ce tutoriel.
  • Vous avez une certaine expérience de travail avec React / Redux - sinon, vous devriez revenir après avoir fait le didacticiel react et le didacticiel redux.

Dans ce tutoriel, nous passerons en revue 6 sections ensemble.

  1. Configuration de l'environnement du serveur (rapide)
  2. Configuration de l'application standard Redux
  3. Ajout du client GraphQL (Apollo Client)
  4. Récupérer des données avec une requête GraphQL
  5. Récupérer encore plus de données
  6. Prochaines étapes

1. Configuration de l'environnement du serveur

Tout d'abord, nous avons besoin d'un serveur GraphQL. Le moyen le plus simple d'avoir un serveur en cours d'exécution est de suivre ce didacticiel génial.

Si vous vous sentez paresseux, vous pouvez simplement cloner mon dépôt, qui est presque le même serveur que vous auriez si vous suiviez le tutoriel vous-même. Le serveur prend en charge les requêtes GraphQL pour récupérer les données d'une base de données SQLite.

Lançons-le et voyons s'il fonctionne correctement:

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

Le serveur doit être exécuté sur // localhost: 8080 / graphql. Accédez à cette page et voyez si vous obtenez une interface GraphiQL fonctionnelle avec des résultats comme celui-ci:

GraphiQL vous permet de tester différentes requêtes et de voir immédiatement la réponse que vous obtenez du serveur. Si nous ne voulons pas du nom de famille d'un auteur et d'un message de cookie de fortune dans une réponse, nous pouvons mettre à jour la requête comme ci-dessous:

Et c'est exactement ce que nous aimons. Nous avons confirmé que notre serveur fonctionnait bien et renvoyait de bonnes réponses, alors commençons à créer le client.

2. Configuration de l'application passe-partout redux

Pour plus de simplicité, nous utiliserons un passe-partout redux afin que nous puissions obtenir toute la configuration (par exemple Babel, webpack, CSS, etc.) gratuitement. J'aime ce passe-partout car sa configuration est facile à suivre et est uniquement côté client - ce qui le rend parfait pour ce didacticiel.

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

Naviguons vers // localhost: 3000 / pour voir si le serveur client est en cours d'exécution.

Yay! Le client est en cours d'exécution. Il est temps pour nous de commencer à ajouter un client GraphQL. Encore une fois, notre objectif est de récupérer facilement les données du serveur et de les rendre dans la page de destination (HomeView) sans trop d'effort en utilisant des requêtes GraphQL.

3. Ajout du client GraphQL (Apollo Client)

Installez les packages apollo-client, react-apollo et graphql-tag.

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

Ensuite, ouvrez le fichier src / containers / AppContainer.js, la racine de notre application Redux. C'est là que nous transmettons le magasin redux aux composants enfants, en utilisant le fournisseur 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

Nous devons initialiser un ApolloClient et remplacer le fournisseur de react-redux par 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

C'est ça! Nous venons d'ajouter un client GraphQL à une application Redux simple aussi facilement.

Allons-y et essayons notre première requête GraphQL.

4. Récupération de données avec des requêtes GraphQL

Ouvrez 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 est un conteneur Redux conventionnel (composant intelligent). Pour utiliser les requêtes GraphQL au lieu des répartiteurs d'actions pour récupérer des données, nous apporterons quelques modifications ensemble.

  1. Supprimez complètement mapDispatchToProps () et mapStateToProps ().
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. Ajoutez mapQueriesToProps () et définissez une requête GraphQL qui récupérera les informations sur l'auteur. Remarquez que c'est exactement la même requête que nous avons testée au début en utilisant l'interface GraphIQL sur le serveur.

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. Remplacez connect from react-redux par connect from react-apollo et passez mapQueriesToProps comme argument. Une fois que mapQueriesToProps est connecté à ApolloClient, la requête récupère automatiquement les données du backend lorsque HomeView est rendu, et transmet les données via les accessoires.

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. Rendez les données transmises par les accessoires:

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