Graphql Client with React & Apollo

Héla Ben Khalfallah
6 min readJun 16, 2020

--

React JS, Apollo Graphql Client

Last time we had seen together how to do a Nodejs graphql server using Apollo :

We continue our trip inside graphql and this time we will focus on client.

We will use :

  • React JS
  • Apollo Graphql Client (hooks version)
  • ant design to have beautiful UI
React — Apollo Graphql Client

Project init

Create project

npx create-react-app react-graphql-clientnpm install apollo-boost @apollo/react-hooks graphqlnpm install antd

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import './index.css';
import 'antd/dist/antd.css';

ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Apollo Client Init

import React from 'react';
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from '@apollo/react-hooks';

const localGraphQL = "http://localhost:5000/graphql";
const client = new ApolloClient({
uri: localGraphQL
});


function App() {
return (
<ApolloProvider client={client}>
<div>
App
</div>
</ApolloProvider>
);
}

export default App;

User list (query all)

View

import React from 'react';
import { useQuery } from '@apollo/react-hooks';
import {
List,
Button,
} from 'antd';
import {
UserQueries,
}from '../graphql';

const {
GET_USERS,
GET_USERS_SKIP,
GET_USERS_INCLUDE,
} = UserQueries;


const UsersListView = () => {
const {
loading,
error,
data,
refetch,
} = useQuery(GET_USERS, {
// pollInterval: 500,
// fetchPolicy: 'cache-and-network'
});



if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
if (!data || !data.users) return <p>Empty :(</p>;

return (
<>
<List
bordered
dataSource={data.users}
renderItem={user => {
const {
firstName,
lastName,
email
} = user;
return (
<List.Item>
<List.Item.Meta
avatar={null}
title={`${firstName} ${lastName}`}
description={email}
/>
</List.Item>
)
}}
/>
<Button
type="primary"
onClick={() => refetch()}
style={{ margin: '1rem' }}
>
Refetch!
</Button>
</>)
};

export default UsersListView;
get all users — client
get all users — server

Query

import { gql } from 'apollo-boost';const GET_USERS = gql`
{
users {
id
firstName
lastName
birthday
email
phone
username
posts {
createdAt
text
user
}
}
}
`;

User details (query by email)

View

import React from 'react';
import { useQuery } from '@apollo/react-hooks';
import { Descriptions } from 'antd';
import {
UserQueries,
}from '../graphql';

const {
GET_USER,
} = UserQueries;


const UserDetailsView = ({
email,
}) => {
const {
loading,
error,
data,
} = useQuery(GET_USER, {
variables: { email },
});


if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
if (!data || !data.user) return <p>Empty :(</p>;

const {
firstName,
lastName,
username,
} = data.user;

return (
<div>
<Descriptions title="User Details">
<Descriptions.Item label="username">{username}</Descriptions.Item>
<Descriptions.Item label="First Name">{firstName}</Descriptions.Item>
<Descriptions.Item label="Last Name">{lastName}</Descriptions.Item>
<Descriptions.Item label="Birthday">22/02/1299</Descriptions.Item>
</Descriptions>
</div>);
};


export default UserDetailsView;
get user by email — client
get user by email — server

Query

const GET_USER = gql`
query User($email: String!) {
user(email: $email) {
id
firstName
lastName
birthday
email
phone
username
password
posts {
createdAt
text
user
}
}
}
`;

User add form (mutation)

View

import React, { useState } from 'react';
import { useMutation } from '@apollo/react-hooks';
import {
Form,
Input,
Button,
} from 'antd';
import {
UserMutations,
}from '../graphql';


const {
ADD_USER,
} = UserMutations;

const UserAddView = () => {
const [
addUser,
{loading}
] = useMutation(ADD_USER, { errorPolicy: 'all' });
const [data, setData ] = useState('');
const [error, setError ] = useState('');

const handleSubmit = async (values) => {
try {
const { data } = addUser({
variables: {
...values,
}})

setData(data)
}catch (e) {
setError(e)
}
}

if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;

const onFinish = values => {
handleSubmit(values);
};

return (
<Form
name="basic"
onFinish={onFinish}
>
<Form.Item
label="First Name"
name="firstName"
rules={[{ required: true, message: 'Please input your first name!' }]}
>
<Input />
</Form.Item>

<Form.Item
label="Last Name"
name="lastName"
rules={[{ required: true, message: 'Please input your last name!' }]}
>
<Input />
</Form.Item>

<Form.Item
label="Email"
name="email"
rules={[{ required: true, message: 'Please input your email!' }]}
>
<Input />
</Form.Item>

<Form.Item
label="Username"
name="username"
rules={[{ required: true, message: 'Please input your username!' }]}
>
<Input />
</Form.Item>

<Form.Item
label="Password"
name="password"
rules={[{ required: true, message: 'Please input your password!' }]}
>
<Input.Password />
</Form.Item>

<Form.Item >
<Button
type="primary"
htmlType="submit"
>
Submit
</Button>
</Form.Item>
</Form>
)
};

export default UserAddView;
user add — client
user add — server

Mutation

import { gql } from 'apollo-boost';

const ADD_USER = gql`
mutation AddUser (
$firstName: String!
$lastName: String!
$phone: String
$email: String!
$username: String!
$password: String!
) {
addUser(firstName: $firstName, lastName: $lastName, phone: $phone, email: $email, username: $username, password: $password) {
id
firstName
lastName
birthday
phone
email
username
}
}
`;

App

import React from 'react';
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from '@apollo/react-hooks';
import {
Divider,
} from 'antd';
import {
UsersListView,
UserDetailsView,
UserAddView,
} from './components';

const localGraphQL = "http://localhost:5000/graphql";
const client = new ApolloClient({
uri: localGraphQL
});


function App() {
return (
<ApolloProvider client={client}>
<div>
<Divider plain>User List</Divider>
<UsersListView />
<Divider plain>User Details</Divider>
<UserDetailsView
email="alberteinsten@gmail.com"
/>

<Divider plain>Add new user</Divider>
<UserAddView />
</div>
</ApolloProvider>
);
}

export default App;

Graphql directives

1. skip

View

const {
loading,
error,
data,
refetch,
} = useQuery(GET_USERS_SKIP, {
variables: {
skipEmail: true,
},
});

Query

const GET_USERS_SKIP = gql`
query Users($skipEmail: Boolean!) {
users{
id
firstName
lastName
birthday
email @skip(if: $skipEmail)
phone
username
posts {
createdAt
text
user
}
}
}
`;

skip : GraphQL execution skips the field email if skipEmail = true by not calling the resolver.

2. include

View

const {
loading,
error,
data,
refetch,
} = useQuery(GET_USERS_INCLUDE, {
variables: {
includeEmail: false,
},
});

Query

const GET_USERS_INCLUDE = gql`
query Users($includeEmail: Boolean!) {
users{
id
firstName
lastName
birthday
email @include(if: $includeEmail)
phone
username
posts {
createdAt
text
user
}
}
}
`;

include : Calls resolver for annotated field if true.

Graphql Fragment

Let’s look at these queries :

const GET_USERS = gql`
{
users {
id
firstName
lastName
birthday
email
phone
username
posts {
createdAt
text
user
}
}
}
`;
const GET_USER = gql`
query User($email: String!) {
user(email: $email) {
id
firstName
lastName
birthday
email
phone
username
password
posts {
createdAt
text
user
}
}
}
`;

What’s wrong ?

Each time we need user informations we duplicate this part :

      id
firstName
lastName
birthday
email
phone
username
password

To avoid duplication we can use Fragment.

Fragment definition :

const USER_PROFILE = gql`
fragment UserProfile on UserType {
id
firstName
lastName
birthday
email
phone
username
}
`;

UserType is the schema defined in graphql server :

type UserType {
id: ID!
firstName: String! @upper
lastName: String! @upper
birthday: DateTime
phone: String
email: String!
username: String!
password: String!
isConnected: Boolean
numberOfFollowers: Int
login: String @deprecated(reason: "Use \`username\`.")
posts: [PostType]
}

Fragment calls :

const GET_USERS = gql`
{
users {
...UserProfile
posts {
createdAt
text
user
}
}
}
${USER_PROFILE}
`;

const GET_USER = gql`
query User($email: String!) {
user(email: $email) {
...UserProfile
posts {
createdAt
text
user
}
}
}
${USER_PROFILE}
`;

Project source code

Thank you for reading my story.

You can find me at :

Twitter : https://twitter.com/b_k_hela

Github : https://github.com/helabenkhalfallah

--

--

Héla Ben Khalfallah
Héla Ben Khalfallah

Written by Héla Ben Khalfallah

Hello! I'm Héla Ben Khalfallah. I'm a software engineer with a wealth of experience in web solutions, architecture, frontend, FrontendOps, and leadership.

No responses yet