GraphQL is a data query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL gives clients the power to ask for exactly what they need and nothing more, and enables powerful developer tools (Graphiql).
GraphQL APIs are organized in terms of types and fields, not endpoints. It uses types to ensure a client only asks for what's possible and provides clear and helpful errors.
With GraphQL we create a data model, or Schema
Schema: A representation of a plan or theory in the form of a model
Apollo Client is a library that was built for seemlessly interacting with GraphQL on the client side.
express
body-parser
axios
express-graphql
: Creates an Express server that can run a GraphQL APIgraphql
: Gives you the tools to create a schema and mutations
apollo-boost
: Package containing everything you need to set up Apollo Clientreact-apollo
: View layer integration for Reactgraphql-tag
: Necessary for parsing your GraphQL queriesgraphql
: Also parses your GraphQL queries
-
In this project we will use the GraphQL Query Language to structure our data on the server and Apollo to interact with GraphQL on the client (app). We will interact with the
Star Wars
API. -
For basic styling and organization we have some files and folders provided for us.
-
server/graphql/model.js
is where our data for this project is stored, and it was pulled from the Star Wars API.
-
Fork and clone this repository
-
Run
yarn
ornpm install
Starting with the server side, we need to add our dependencies
-
yarn add graphql
-
yarn add express-graphql graphql
Now let's require these dependencies in the server and use them
-
require
express-graphql
asgraphqlHTTP
in your server -
apply
graphqlHTTP
as top-level middleware as a route handlerapp.use()
-
the first argument should be an endpoint
/graphql
and the second argument should begraphqlHTTP
invoked with a configuration Object as an argument -
inside the configuration Object:
// ? = optional
graphqlHTTP({
schema: YOUR_SCHEMA, // <-- required
graphiql?: ?boolean,
rootValue?: ?any,
context?: ?any,
pretty?: ?boolean,
formatError?: ?Function,
validationRules?: ?Array<any>,
})
- We will use the
graphiql
property so we can use the developer tools.
server/index.js
// server/index.js
const graphqlHTTP = require('express-graphql')
// ...
app.use('/graphql', graphqlHTTP({
schema: schema,
graphiql: true
}))
// ...
Let's setup our schema, where most of our logic will take place
-
inside
server/graphql
, create a file namedschema.js
-
we need to access our data, so
require
ourserver/graphql/model.js
insideschema.js
-
we need to require
graphql
and destructure a handful of functions{ GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLInt, GraphQLList, GraphQLNonNull }
server/schema.js
// server/schema.js
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLList,
GraphQLNonNull
} = require('graphql')
let characters = require('./model')
// ...
We need to require our schema.js
file inside our index.js
server file in order for graphqlHTTP
to access it via the schema
property
-
require
schema.js
insideserver/index.js
and save it to a variable namedschema
-
add the
schema
variable to theschema
property ongraphqlHTTP
server/index.js
// server/index.js
// ...
const schema = require('./graphql/schema')
// ...
app.use('/graphql', graphQLExpress({
schema: schema, // <-- add schema to configuration Object
graphiql: true
}))
// ...
Back to schema.js
, we are going to create our root query Object
-
create a variable named
Query
and set it equal to anew GraphQLObjectType()
-
inside the
GraphQLObjectType
, provide it an Object with:- a
name
property equal to a stringQuery
- a
fields
property equal to a function that returns an Object- this is where we declare the queries available on the API
- a
-
at the bottom of
schema.js
let's export:
module.exports = new GraphQLSchema({
query: Query // <-- our Query variable
})
server/schema.js
// server/schema.js
// ...
const Query = new GraphQLObjectType({
name: 'Query',
fields: () => {
return {
// define the keyword queries
}
}
})
module.exports = new GraphQLSchema({
query: Query
})
Our data is full of Star Wars characters so let's create a people
query so we can fetch all people
-
inside our
fields
Object, add a property calledpeople
and set it to an empty Object -
inside our
people
Object, give it atype
property. This defines how the query should be structured. In this case we want anew GraphQLList()
ofPersonType
, we will define thePersonType
in the next step -
now let's add a
resolve
property that is a function. This is where we can do our functionality for the query. -
we want the
resolve
function to return allcharacters
server/schema.js
// server/schema.js
// ...
const Query = new GraphQLObjectType({
name: 'Query',
fields: () => {
return {
// start new code
people: {
type: new GraphQLList(PersonType),
resolve: () => {
return characters
}
}
// end new code
}
}
})
// ...
We need to define our PersonType
so our people
query knows how to structure the data properly
- create a variable called
PersonType
and set it equal to anew GraphQLObjectType()
const PersonType = new GraphQLObjectType({
// ...code
})
-
add a
name
property and set it equal to a stringPerson
-
add a
fields
property so we can set the fields available on thePersonType
-
in
fields
for now, includeid, name, height
properties -
these properties require a
type
definition
server/schema.js
// server/schema.js
// ...
const PersonType = new GraphQLObjectType({
name: 'Person',
fields: () => {
return {
id: { type: GraphQLInt },
name: { type: GraphQLString },
height: { type: GraphQLInt }
}
}
})
// ...
now let's create another type
that we can add to our PersonType
later
-
create a mew variable
MovieType
-
with the
name
set to a stringMovie
-
copy a link from the
films
Array and make a call to see the API structure and details for each film -
add
name, releaseDate
properties and their respectivetype
's
server/schema.js
// server/schema.js
// ...
const MovieType = new GraphQLObjectType({
name: 'Movie',
fields: () => {
return {
name: { type: GraphQLString },
releaseDate: { type: GraphQLString }
}
}
})
// ...
because we named our propery releaseDate
instead of release_date
like the original API has it as, we need to convert it with a resolve
function
-
add a
resolve
function to thereleaseDate
property -
add
person
as an argument forresolve
, this refers to the parent value -
return
person.release_date
server/schema.js
//server/schema.js
// ...
const MovieType = new GraphQLObjectType({
name: 'Movie',
fields: () => {
return {
name: { type: GraphQLString },
// start new code
releaseDate: {
type: GraphQLString,
resolve: person => {
return person.release_date
}
}
// end new code
}
}
})
// ...
now that we have our MovieType
we can add it to our PersonType
-
add a new field,
films
to thePersonType
-
set the
type
tonew GraphQLList(MovieType)
-
add a
resolve
function so we can make an axios call for the detailed information from the original API -
yarn add axios
and requireaxios
insideschema.js
server/schema.js
//server/schema.js
// ...
const PersonType = new GraphQLObjectType({
name: 'Person',
fields: () => {
return {
id: { type: GraphQLInt },
name: { type: GraphQLString },
height: { type: GraphQLInt },
films: {
type: new GraphQLList(MovieType),
resolve: (person) => {
// if films array is empty return an empty array
return !person.films.length
? []
// otherwise map over it and make the axios call for each link
: person.films.map(film => {
return axios.get(film).then(res => res.data)
})
}
}
}
}
})
// ...
we are now going to add another GraphQLObjectType
for HomeWorld
-
create a variable
HomeWorldType
set to anew GraphQLObjectType
-
give it a
name
equal to a stringHomeWorld
-
give it a
fields
function -
give the returned
fields
objectname, climate, population
properties, which all can have a stringtype
server/schema.js
// server/schema.js
// ...
const HomeWorldType = new GraphQLObjectType({
name: 'HomeWorld',
fields: () => {
return {
name: { type: GraphQLString },
climate: { type: GraphQLString },
population: { type: GraphQLString }
}
}
})
// ...
server/schema.js so far
// server/schema.js
const axios = require('axios')
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLList,
GraphQLNonNull
} = require('graphql')
let characters = require('./model')
const PersonType = new GraphQLObjectType({
name: 'Person',
fields: () => {
return {
id: { type: GraphQLInt },
name: { type: GraphQLString },
height: { type: GraphQLInt },
films: {
type: new GraphQLList(MovieType),
resolve: (person) => {
return !person.films.length
? []
: person.films.map(film => {
return axios.get(film).then(res => res.data)
})
}
}
}
}
})
const MovieType = new GraphQLObjectType({
name: 'Movie',
fields: () => {
return {
title: { type: GraphQLString },
releaseDate: {
type: GraphQLString,
resolve: person => {
return person.release_date
}
}
}
}
})
const HomeWorldType = new GraphQLObjectType({
name: 'HomeWorld',
fields: () => {
return {
name: { type: GraphQLString },
climate: { type: GraphQLString },
population: { type: GraphQLString }
}
}
})
const Query = new GraphQLObjectType({
name: 'Query',
fields: () => {
return {
people: {
type: new GraphQLList(PersonType),
resolve: () => {
return characters
}
}
}
}
})
module.exports = new GraphQLSchema({
query: Query
})
now lets add a homeWorld
field to the PersonType
-
set the
type
for thehomeWorld
property to theHomeWorldType
-
add a
resolve
function with aperson
parameter so we can make another axios call to get the homeworld details from the original API
server/schema.js
// server/schema.js
// ...
const PersonType = new GraphQLObjectType({
name: 'Person',
fields: () => {
return {
id: { type: GraphQLInt },
name: { type: GraphQLString },
height: { type: GraphQLInt },
films: {
type: new GraphQLList(MovieType),
resolve: (person) => {
return !person.films.length
? []
: person.films.map(film => {
return axios.get(film).then(res => res.data)
})
}
},
homeWorld: {
type: HomeWorldType,
resolve: (person) => {
return axios.get(person.homeworld).then(res => res.data)
}
}
}
}
})
// ...
lets add a person
query so we can get a specific character
-
after our
people
field onQuery
add another field,person
-
type
should be equal toPersonType
-
add an
args
property so that we can give ourresolve
some arguments to find a specific character -
the
args
we need is anid
, and each argument also needs atype
-
the
type
should specify that it cannot benull
like so:
// ...
person: {
type: PersonType,
args: { id: { type: GraphQLNonNull(GraphQLInt) } }
}
// ...
- now add a
resolve
that takes inargs
as a second parameter so that it has access to the id:
resolve: (parentVal, args) => {
// code
}
server/schema.js
// server/schema.js
// ...
const Query = new GraphQLObjectType({
name: 'Query',
fields: () => {
return {
people: {
type: new GraphQLList(PersonType),
resolve: () => {
return characters
}
},
person: {
type: PersonType,
args: { id: { type: GraphQLNonNull(GraphQLInt) } },
resolve: (parentVal, args) => {
return characters.find(person => person.id === args.id)
}
}
}
}
})
// ...
now that you can see how it can be used to stucture your data, let's see about mutating the data
-
create a variable
Mutation
equal to anew GraphQLObjectType
-
set a
name
property equal to the stringMutation
-
add a
fields
function -
inside the
fields
is where we declare our mutations, create a property nameddeletePerson
set to an Object withtype, args, resolve
properties -
the
type
field is for the return value of theresolve
, what's being returned to the client -
args
are for setting the expected arguments to make this Mutation work -
give
resolve
theargs
as a second parameter so we can access them inside the function -
add a
mutation
property to ourmodule.exports
and set it equal to theMutation
variable
server/schema.js
// server/schema.js
// ...
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: () => {
return {
deletePerson: {
type: PersonType,
args: { id: { type: GraphQLNonNull(GraphQLInt) } },
resolve: (parentVal, args) => {
let character = characters.find(e => e.id === args.id)
characters = characters.filter(person => person.id !== args.id)
return {
id: character.id,
name: character.name
}
}
}
}
}
})
module.exports = new GraphQLSchema({
query: Query,
mutation: Mutation
})
finished server/schema.js
const axios = require('axios')
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLList,
GraphQLNonNull
} = require('graphql')
let characters = require('./model')
const PersonType = new GraphQLObjectType({
name: 'Person',
fields: () => {
return {
id: { type: GraphQLInt },
name: { type: GraphQLString },
height: { type: GraphQLInt },
films: {
type: new GraphQLList(MovieType),
resolve: (person) => {
return !person.films.length
? []
: person.films.map(film => {
return axios.get(film).then(res => res.data)
})
}
},
homeWorld: {
type: HomeWorldType,
resolve: (person) => {
console.log('A SINGLE PERSON OBJECT FROM THE DATA', person)
return axios.get(person.homeworld).then(res => res.data)
}
}
}
}
})
const MovieType = new GraphQLObjectType({
name: 'Movie',
fields: () => {
return {
title: { type: GraphQLString },
releaseDate: {
type: GraphQLString,
resolve: person => {
return person.release_date
}
}
}
}
})
const HomeWorldType = new GraphQLObjectType({
name: 'HomeWorld',
fields: () => {
return {
name: { type: GraphQLString },
climate: { type: GraphQLString },
population: { type: GraphQLString }
}
}
})
const Query = new GraphQLObjectType({
name: 'Query',
fields: () => {
return {
people: {
type: new GraphQLList(PersonType),
resolve: () => {
return characters
}
},
person: {
type: PersonType,
args: { id: { type: GraphQLNonNull(GraphQLInt) } },
resolve: (parentVal, args) => {
return characters.find(person => person.id === args.id)
}
}
}
}
})
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: () => {
return {
deletePerson: {
type: PersonType,
args: { id: { type: GraphQLNonNull(GraphQLInt) } },
resolve: (parentVal, args) => {
let character = characters.find(e => e.id === args.id)
characters = characters.filter(person => person.id !== args.id)
return {
id: character.id,
name: character.name
}
}
}
}
}
})
module.exports = new GraphQLSchema({
query: Query,
mutation: Mutation
})
now moving to the client side, we need to add our dependencies
yarn add apollo-boost react-apollo graphql-tag
we need to give our application access to the Apollo Client
-
inside
src/index.js
lets importApolloClient
fromapollo-boost
-
create a variable
client
and set it equal to anew ApolloClient()
-
give
ApolloClient
a configuration Object with auri
property set to our local server graphql endpointhttp://localhost:3050/graphql
-
to test, let's import
gql
fromgraphql-tag
and write a query now:
client.query({
query: gql`
{
people {
name
}
}
`
}).then(res => console.log(res.data))
-
check your developer console and you should have an array of 10 people!
-
now lets
import { ApolloProvider } from 'react-apollo'
-
wrap
<App />
withApolloProvider
-
give
ApolloProvider
aclient
attribute and set it equal to ourclient
variable
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App'
import './index.css'
import ApolloClient from 'apollo-boost'
import gql from 'graphql-tag' // we don't need this anymore after a successful test
import { ApolloProvider } from 'react-apollo'
const client = new ApolloClient({
uri: 'http://localhost:3050/graphql'
})
// we can delete this query
client.query({
query: gql`
{
people {
name
}
}
`
})
.then(res => console.log(res.data))
// ----
ReactDOM.render(
<ApolloProvider client={ client }>
<App />
</ApolloProvider>, document.getElementById('root')
)
-
create
queries
andmutations
folders insidesrc/components
-
create a
PeopleQuery
component inside thequeries
folder -
create a
DeletePersonMutation
component inside themutations
folder
lets start with the PeopleQuery
component, we need to create the query and render the Query
component provided by react-apollo
-
create a stateless component
PeopleQuery
-
import { Query } from 'react-apollo'
&&import { gql } from 'graphql-tag'
- graphql-tag will allow us to write a template literal tag, explained by example inside
templateLiteralTagExample.js
- graphql-tag will allow us to write a template literal tag, explained by example inside
-
below our imports but above our component, create a variable
GET_PEOPLE
and set it equal to our people query with our desired properties:
// using a template literal tag, we can write in GraphQL query language
const GET_PEOPLE = gql`
query getPeople {
people {
id
name
height
films {
title
}
homeWorld {
name
}
}
}
`
-
return the
Query
component insidePeopleQuery
that takes aquery
prop equal toGET_PEOPLE
-
provide a function to the
Query
children prop to determine what to renderreact-apollo
uses the render prop pattern
-
the function should take in an Object that has
loading, error, data
propertiesApolloClient
tracks error and loading state for you, which will be reflected in theloading
anderror
properties- once the query comes back it is attached to the
data
property
queries/PeopleQuery.js
// queries/PeopleQuery.js
import React from 'react'
import { Query } from 'react-apollo'
import { gql } from 'graphql-tag'
const GET_PEOPLE = gql`
query getPeople {
people {
id
name
height
films {
title
}
homeWorld {
name
}
}
}
`
const PeopleQuery = props => {
return (
<Query query={ GET_PEOPLE }>
{
(loading, error, data) => {
// code
}
}
</Query>
)
}
export default PeopleQuery
conditional renders depending on the state of the data
-
inside our function wrapped by the
Query
component, create someif..
statements forloading
anderror
-
if it's loading return:
<div>
<img className='le-image'
src="https://media.giphy.com/media/GIEXgLDfghUSQ/giphy.gif"
alt="Loading"
/>
</div>
- if there is an error return:
<div>
<img className='le-image'
src="http://www.fico.com/en/blogs/wp-content/uploads/2017/03/Lack-of-Data.gif"
alt="error"
/>
</div>
- otherwise we want to return
props.render(data)
queries/PeopleQuery.js
// queries/PeopleQuery.js
import React from 'react'
import { gql } from 'apollo-boost'
import { Query } from 'react-apollo'
export const GET_PEOPLE = gql`
query getPeople {
people {
id
height
name
films {
title
}
homeWorld {
name
}
}
}
`
const PeopleQuery = props => {
return (
<Query query={ GET_PEOPLE }>
{
({ loading, error, data }) => {
if(loading) {
return (
<div>
<img className='le-image'
src="https://media.giphy.com/media/GIEXgLDfghUSQ/giphy.gif"
alt="Loading"
/>
</div>
)
}
if(error) {
return (
<div>
<img className='le-image'
src="http://www.fico.com/en/blogs/wp-content/uploads/2017/03/Lack-of-Data.gif"
alt="error"
/>
</div>
)
}
return props.render(data);
}
}
</Query>
)
}
export default PeopleQuery
time to see this working!
-
in
App.js
:import PeopleQuery from './queries/PeopleQuery'
-
under the
Header
component, renderPeopleQuery
with arender
prop -
this
render
prop should be a function that returns<List list-{ data.people }/>
components/App.js
// components/App.js
import React, { Component } from 'react'
import Header from './header/Header'
import Tech from './header/Tech'
import List from './List'
import PeopleQuery from './queries/PeopleQuery'
export default class App extends Component {
render() {
return (
<div>
<Header>
<Tech
tech='GraphQL'
image='https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/GraphQL_Logo.svg/2000px-GraphQL_Logo.svg.png'
/>
<Tech
tech='Apollo'
image='https://seeklogo.com/images/A/apollo-logo-DC7DD3C444-seeklogo.com.png'
/>
<Tech
tech='React'
image='https://www.qualium-systems.com/wp-content/uploads/2015/07/icon-reactjs.svg'
/>
</Header>
<div>
<PeopleQuery render={ data => <List list={ data.people } /> } />
</div>
</div>
)
}
}
All we have left is to create our DeletePerson
component and implement it!
-
create a stateless component named
DeletePerson
-
import { Mutation } from 'react-apollo'
-
import { gql } from 'graphql-tag'
-
back in
queries/PeopleQuery
let's exportGET_PEOPLE
-
import { GET_PEOPLE } from '../queries/PeopleQuery'
mutations/DeletePerson.js
// mutations/DeletePerson.js
import React from 'react'
import { Mutation } from 'react-apollo'
import { gql } from 'apollo-boost'
import { GET_PEOPLE } from '../queries/PeopleQuery'
const DeletePerson = props => {
return (
)
}
export default DeletePerson
let's create our mutation
-
create a variable
DELETE_PERSON
and set it equal to thegql
function -
using a template literal tag, define the mutation:
mutation deletePerson($id: Int!) { // Variable: id, Integer REQUIRED
deletePerson(id: $id) {
id
name
}
}
mutations/DeletePerson.js
// mutations/DeletePerson.js
import React from 'react'
import { Mutation } from 'react-apollo'
import { gql } from 'apollo-boost'
import { GET_PEOPLE } from '../queries/PeopleQuery'
export const DELETE_PERSON = gql`
mutation deletePerson($id: Int!) {
deletePerson(id: $id) {
id
name
}
}
`
const DeletePerson = props => {
return (
)
}
export default DeletePerson
time to make the functionality of the component!
-
return the
Mutation
component- the
Mutation
component again, follows the render prop pattern
- the
-
give
Mutation
amutation
prop equal to theDELETE_PERSON
variable -
we also need to provided an
update
prop- this is necessary when adding or deleting.
ApolloClient
uses a cache to be more efficient and we need to tell it to update the cache when we make a change
- this is necessary when adding or deleting.
-
update
is called withcache
as the first argument, and the second is an Object with adata
property containing your mutation result:
<Mutation
mutation={ DELETE_PERSON }
update={(cache, { data: { deletePerson } }) => {
// code
}}
>
</Mutation>
-
cache
has several utility functions that are useful, we are interested in.readQuery()
and.writeQuery()
-
destructure
people
from the result ofcache.readQuery({ query: GET_PEOPLE })
inside theupdate
prop:
let { people } = cache.readQuery({ query: GET_PEOPLE })
- now that we have the people array from the cache, we can alter it and write an updated cache:
const updatedPeople = people.filter(person => person.id !== deletePerson.id)
- then we can write this back to the cache:
cache.writeQuery({
query: GET_PEOPLE,
data: { people: updatedPeople }
})
mutations/DeletePerson.js
// mutations/DeletePerson.js
import React from 'react'
import { Mutation } from 'react-apollo'
import { gql } from 'apollo-boost'
import { GET_PEOPLE } from '../queries/PeopleQuery'
export const DELETE_PERSON = gql`
mutation deletePerson($id: Int!) {
deletePerson(id: $id) {
id
name
}
}
`
const DeletePerson = props => {
return (
<Mutation
mutation={ DELETE_PERSON }
update={(cache, { data: { deletePerson } }) => {
let { people } = cache.readQuery({ query: GET_PEOPLE })
const updatedPeople = people.filter(e => e.id !== deletePerson.id)
cache.writeQuery({
query: GET_PEOPLE,
data: { people: updatedPeople }
})
}}
>
// code
</Mutation>
)
}
export default DeletePerson
provide a function to render in Mutation
- insert a function inbetween the
Mutation
component tags and provide the function withdeletePerson
and destructoredloading
anderror
:
{ (deletePerson, { loading, error }) => {
// code
} }
- now we can give those arguments to
props.children
:
{ (deletePerson, { loading, error }) => {
return (
<div>
{ props.children(loading, error, deletePerson) }
</div>
)
} }
mutations/DeletePerson.js
// mutations/DeletePerson.js
import React from 'react'
import { Mutation } from 'react-apollo'
import { gql } from 'apollo-boost'
import { GET_PEOPLE } from '../queries/PeopleQuery'
export const DELETE_PERSON = gql`
mutation deletePerson($id: Int!) {
deletePerson(id: $id) {
id
name
}
}
`
const DeletePerson = props => {
return (
<Mutation
mutation={ DELETE_PERSON }
update={(cache, { data: { deletePerson } }) => {
let { people } = cache.readQuery({ query: GET_PEOPLE })
const updatedPeople = people.filter(e => e.id !== deletePerson.id)
cache.writeQuery({
query: GET_PEOPLE,
data: { people: updatedPeople }
})
}}
>
{ (deletePerson, { loading, error }) => {
return (
<div>
{ props.children(loading, error, deletePerson) }
</div>
)
} }
</Mutation>
)
}
export default DeletePerson
now to implement our mutation in our Card
component where it's rendered on each person in the people Array
-
in
Card.js
:import DeletePersonMutation from '../components/mutations/DeletePersonMutation'
-
provide a function as
props.children
toDeletePersonMutation
-
pass in
loading
,error
, anddeletePerson
-
return a
div
with abutton
inside that has anonClick
that usesdeletePerson
-
pass
deletePerson
an Object with avariables
property:
onClick={ () => deletePerson({ variables: { id: this.props.id } })
- short circuit
loading
anderror
inside thediv
components/Card.js
// components/Card.js
import React, { Component } from 'react'
import DeletePersonMutation from '../components/mutations/DeletePersonMutation'
export default class Card extends Component {
render() {
return (
<div className='card'>
<h4>Character:</h4>
<p>{this.props.name}</p>
<p>{this.props.height}</p>
<br />
<h4>Homeworld</h4>
<p>{this.props.homeWorld.name}</p>
<br />
<h4>Number of Films</h4>
<p>{this.props.films.length}</p>
<br />
<DeletePersonMutation>
{ (loading, error, deletePerson) => {
return (
<div>
<button
onClick={ () => deletePerson({ variables: { id: this.props.id } }) }
>
Delete
</button>
{ loading && <p>Loading...</p> }
{ error && <p>Error...</p> }
</div>
)
} }
</DeletePersonMutation>
</div>
)
}
}