Running GraphQL server to connect to.
First let’s have GraphQL server running so we could connect to.
How to do this:
- Install MongoDB because our version of GraphQL is using it and we will need a real database in the future tutorials. So let’s do it now. If you are on MAC and have Homebrew installed this is as simple as executing
brew install mongodb
from the command line. - Download an run GraphQL setup as well. This will install Database settings that we will need in MogoDB.
yarn setup
then run the server:
yarn start
Now if you point your browser to http://localhost:4001/graphql you will be able to see GraphQL GUI and you could execute queries there, but this is not the subject of this tutorial. If you are interested of how to set up GraphQL with Express server you could visit this article.
Creating ‘Smart’ component.
The concept of ‘smart’ and ‘dumb’ components is simple: We separate the data fetching from presentational layer by dividing the components into ‘presentational’ which only deal with how to show the data, and ‘container’ components, which only work is to fetch the data from some source. In our case this will be Apollo provider. (read more …)
Installing dependencies.
yarn add graphql graphql-tag react-apollo apollo-client apollo-link-http apollo-cache-inmemory
- graphql – query language for APIs created by Facebook.
- graphql-tag – utilities for parsing GraphQL queries.
- react-apolllo – utilities that fetch data from GraphQL server and use it in building complex and reactive UIs using the React framework.
- apollo-client – GraphQL client with integrations for React, Angular, and more.
- apollo-link-http – interface for modifying control flow of GraphQL requests and fetching GraphQL results
- apollo-cache-inmemory – Tool for caching GraphQL client with integrations for React, Angular, and more.
yarn add graphql graphql-tag react-apollo apollo-link-http apollo-client apollo-cache-inmemory
Now if we have all moving peaces in place, we could start building our first ‘connected‘ component, that will fetch data from QgraphQL.
Creating Apollo provider.
Let’s add all necessary components that we will need to connect to GraphQL, and wrap the rotes into Apollo provider .
./src/components/App/index.js
import React, { Component } from 'react'; import Home from '../Home'; import Greetings from '../Greetings'; import About from '../About'; import DogsCatalog from '../../containers/DogsCatalog'; import Header from '../Header'; import { ApolloProvider } from 'react-apollo'; import { ApolloClient } from 'apollo-client'; import { HttpLink } from 'apollo-link-http'; import { InMemoryCache } from 'apollo-cache-inmemory'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import styles from './styles.scss'; export default class App extends Component { render() { const GRAPHQL_URL = 'http://localhost:4001/graphql'; const client = new ApolloClient({ link: new HttpLink({ uri: GRAPHQL_URL }), cache: new InMemoryCache() }); return ( <div className={styles.appWrapper}> <ApolloProvider client={client}> <Router> <Header /> <Switch> <Route exact path="/home" user="Test" component={Home} /> <Route exact path="/greetings" user="Test" component={Greetings} /> <Route exact path="/dogs-catalog" component={DogsCatalog} /> <Route exact path="/about" component={About} /> </Switch> </Router> </ApolloProvider> </div> ); } }
what we just did:
– added all necessary components that we will need to connect to GraphQL (lines 7 – 10) .
– Import our new Dogs-Catalog component that we are going to build (line 5).
– created Apollo component, and pass link and cache parameters (lines 15-19).
– wrap the routes into Apollo provider.
– add new route to show out new ‘Dogs Catalog’ component.
Also make sure that you are adding navigation section to the Header component.
import React from 'react'; import { Link } from 'react-router-dom'; const styles = require('./styles.scss'); const Header = ( {title} ) => ( <div> <div className={styles.wrapper}> <ul> <li><Link to='/home'>HOME</Link></li> <li><Link to='/greetings'>GREETINGS</Link></li> <li><Link to='/dogs-catalog'>DOGS CATALOG</Link></li> <li><Link to='/about'>ABOUT</Link></li> </ul> </div> </div> ); export default Header;
Creating connected component.
We will create a simple component, that will fetch data from GraphQL and will show different dogs breeds depending of which one user will choose.
Create new folder mkdir ./src/containers
and add the new index.js for the new ‘Dogs Catalog’ component.
./src/containers/DogsCatalog/index.js
import React from 'react'; import gql from 'graphql-tag'; import { Query } from 'react-apollo'; const GET_DOG = gql` query { getDogByBreed(breed: "labrador") { id breed displayImage } } ` const DogCatalog = () => ( <Query query={GET_DOG}> {({ loading, error, data }) => { if (loading) return <div>Loading...</div>; if (error) return <div>Error!</div>; return ( <div> <span>breed: </span> <span>{data.getDogByBreed.breed}</span> <br /> <img src={data.getDogByBreed.displayImage} /> </div> ) }} </Query> ) export default DogCatalog;
what we just did:
– we created const GET_DOG
query and we ‘asked’ GraphQL to getDogByBreed
where breed = labrador
(for more information of what queries you could execute, you could go to http://localhost:4001/graphql and look at the right side where the schema is defined or read more here)
– we wrapped our new component in Query
tag, which passes down three properties: loading
, error
and data
– we showed the appropriate messages depending of the status of loading
and error
and on success we rendered the component, showing the returned data
Fire up the server using you prefered method, and navigate to http://localhost:8080/dogs and enjoy your new component, showing an image of nice black Labrador!
Refactoring the component.
Move the GraphQL query to a separate file.
Keeping things well organized is very important for maintainability and scaling reasons, so let’s move the query in a separate file.
./src/containers/DogsCatalog/index.js
import React from 'react'; import { graphql } from 'react-apollo'; import query from './query'; const DogCatalog = (props) => { if(typeof props.data.getDogByBreed === 'undefined') { return ( <p>Loading ...</p> ); } return( <div> <span>breed: </span> <span>{props.data.getDogByBreed.breed}</span> <br /> <img src={props.data.getDogByBreed.displayImage} /> </div> ); } let breed = 'labrador'; export default graphql(query, { options: { variables: { breed: breed } } })(DogCatalog);
Create query.js file with the query.
./src/containers/DogsCatalog/query.js
import gql from 'graphql-tag'; const query = gql` query getDogByBreed($breed: String) { getDogByBreed(breed: $breed) { id breed displayImage } } ` export default query;
what we just did:
– we moved the query to a separate file query.js
– we include the query (line 3)
– we wrapped the DogsCatalog component into GraphQL component, which will execute the query once the component is mounted, and will pass the result back into props.data
.
Adding high order component with dogs breed buttons grid.
What we just created is more like a Dog Details component than Dog catalog, so let’s create a new folder under ./src/containers/DogsCatalog and name it DogDetails, and move ./src/containers/DogsCatalog/index.js and ./src/containers/DogsCatalog/query.js to the new DogDetails folder.
Then create a new ./src/containers/DogsCatalog/index.js file which will show a gird with buttons, from where users can select which dog breed should be displayed into DogDetails component. Then we will pass the selected breed to the DogDetails component.
./src/containers/DogsCatalog/index.js
import React, { Component } from 'react'; import { graphql } from 'react-apollo'; import DogDetails from './DogDetails'; import query from './query'; class DogsCatalog extends Component { constructor(props) { super(props); this.state = { breed: "labrador" } } handleClick(breedType) { this.setState({ breed: breedType }); } render() { if(typeof this.props.data.getDogsList == 'undefined') { return( <div>Loading ... </div> ); } return( <div> <p>Dogs catalog</p> <div> {this.props.data.getDogsList.map( (dog) => { return (<button key={dog.id} onClick={ () => { this.handleClick(dog.breed) } }>{dog.breed}</button>); })} </div> <DogDetails breed={this.state.breed} /> </div> ); } } export default graphql(query, {})(DogsCatalog);
./src/containers/DogsCatalog/query.js
import gql from 'graphql-tag'; const query = gql` query getDogsList { getDogsList { id breed } } ` export default query;
./src/containers/DogsCatalog/DogDetails/index.js
import React from 'react'; import { graphql } from 'react-apollo'; import query from './query'; const DogDetails = (props) => { if(typeof props.data.getDogByBreed === 'undefined') { return ( <p>Loading ...</p> ); } return( <div> <span>breed: </span> <span>{props.data.getDogByBreed.breed}</span> <br /> <img src={props.data.getDogByBreed.displayImage} /> </div> ); } let breed = 'labrador'; export default graphql(query, { justADumFunction(ThePropsValuesPassedIntoTheFunction) { return { variables: { breed: ThePropsValuesPassedIntoTheFunction.breed, }, }; }, })(DogDetails);
./src/containers/DogsCatalog/DogDetails/query.js
import gql from 'graphql-tag'; const query = gql` query getDogByBreed($breed: String) { getDogByBreed(breed: $breed) { id breed displayImage } } ` export default query;
We could have download all dog details and pass it to the DogDetails component and save the second query, but for the purpose of exercising working with queries we are not going to do this. Besides imagine that we might need a lot more data for each breed then it’s not wise to pre-load it since this might take significant amount of time.
Beautify the component with SASS.
Let’s add some css.
create new file
./src/containers/DogsCatalog/styles.scss
.Wrapper { img { width: 100%; } .Buttons { display: grid; grid-template-columns: repeat(6, 1fr); grid-gap: 8px; max-width: 1200px; width: 100%; margin: 0 auto; } .Buttons > button { background: efefef; border-radius: 5px; transition: 0.5s; } .Buttons > button:hover { background: silver; color:white; cursor: pointer; } }
and edit dogs component adding the css styles:
./src/containers/DogsCatalog/index.js
/* eslint-disable no-debugger */ import React, { Component } from 'react'; import { graphql } from 'react-apollo'; import DogDetails from './DogDetails'; import query from './query'; const styles = require('./styles.scss'); class DogsCatalog extends Component { constructor(props) { super(props); this.state = { breed: "labrador" } } handleClick(breedType) { this.setState({ breed: breedType }); } render() { if(typeof this.props.data.getDogsList == 'undefined') { return( <div>Loading ... </div> ); } return( <div className={styles.Wrapper}> <p>Dogs catalog</p> <div className={styles.Buttons}> {this.props.data.getDogsList.map( (dog) => { return (<button key={dog.id} onClick={ () => { this.handleClick(dog.breed) } }>{dog.breed}</button>); })} </div> <DogDetails breed={this.state.breed} /> </div> ); } } export default graphql(query, {})(DogsCatalog);
Now our component looks much better.
We could have also move the presentational layer to the components instead, but since this is pretty small amount of code, we are not going to complicate it and will keep the presentational layer here for now.