Build a basic CRUD frontend application with React & GraphQL.
-
Setup an empty project using
Create React App
npm init react-app react-graphql-sample --use-npm --template typescript cd react-graphql-sample
-
Start the server in watch mode.
npm start
A Browser will popup with http://localhost:3000, and you should see the default React web page.
If you cannot start the server, make sure the global
create-react-app
tool is removed then redo from Step 1 again.npm -g remove create-react-app yarn global remove create-react-app
-
Create the
src/component/UserList.tsx
component.import React, { useState } from "react"; export const UserList = () => { const [users] = useState<Array<{ id: string; name: string }>>([ { id: "1", name: "John" }, { id: "2", name: "Mary" }, ]); return ( <ul> {users.map((user) => ( <li key={user.id}> {user.name} <button>x</button> </li> ))} </ul> ); };
-
Create the
src/component/AddUser.tsx
component.import React, { useState } from "react"; export const AddUser = () => { const [name, setName] = useState(""); return ( <div> <input value={name} onChange={(e) => setName(e.target.value)} /> <button disabled={!name}>Add</button> </div> ); };
-
Update the main
App.tsx
as follow.import React from "react"; import "./App.css"; import { AddUser } from "./component/AddUser"; import { UserList } from "./component/UserList"; function App() { return ( <div className="App"> <header className="App-header"> <UserList /> <AddUser /> </header> </div> ); } export default App;
Start the React server by npm start
, you should see the UserList
and AddUser
components correctly rendered.
The previous NodeJS GraphQL Backend Tutorial will be reused as the GraphQL backend.
Install Apollo Boost and Apollo React Hooks client libraries.
npm install apollo-boost @apollo/react-hooks @apollo/react-testing graphql
for more information, please see Apollo React Get Started.
GraphQL Code Generator
Tool will be used to generate the React client code out of backend's GraphQL schema, you can use the generated code to access the GraphQL backend easily.
npm install -D @graphql-codegen/cli
npx graphql-codegen init
The following questions will be asked during the setup, please answer them accordingly.
- What type of application are you building?
Application built with React
- Where is your schema?: (path or url)
http://localhost:4000/graphql
- Where are your operations and fragments?:
src/**/*.graphql
- Pick plugins:
TypeScript (required by other typescript plugins)
TypeScript Operations (operations and fragments)
TypeScript React Apollo (typed components and HOCs)
- Where to write the output:
src/generated/graphql.tsx
- Do you want to generate an introspection file?
No
- How to name the config file?
codegen.yml
- What script in package.json should run the codegen?
codegen
After generated the codegen.yml
file, the package.json
will also be updated to include the plugins packages, please run the following to install them.
npm install
Add the following config
at the end of codegen.yml
file to enable the code generation for React Hooks.
...
generates:
src/generated/graphql.tsx:
plugins:
...
config:
withHOC: false
withComponent: false
withHooks: true
In this tutorial we will only use the React Hooks, therefore the
HOC
andComponent
code generation are disabled.
Create the following GraphQL query and mutation files for the generator.
-
src/graphql/Users.graphql
query Users { users { id name nickName } }
-
src/graphql/CreateUser.graphql
mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id name nickName } }
-
src/graphql/DeleteUser.graphql
mutation DeleteUser($id: ID!) { deleteUser(id: $id) }
Start the GraphQL server from previous NodeJS GraphQL Backend Tutorial.
cd nodejs-graphql-sample
npm run start:dev
Please make sure the GraphQL server is running on http://localhost:4000/graphql.
Now we can do the actually generation now.
npm run codegen
Notes that a Typescript file src/generated/graphql.tsx
will be generated, and the Apollo React Hooks are ready to import.
-
Add Apollo Provider into
src/index.tsx
.import { ApolloProvider } from "@apollo/react-hooks"; import ApolloClient from "apollo-boost"; ... const client = new ApolloClient({ uri: (process.env.SERVER_URL || "http://localhost:4000") + "/graphql" }); ReactDOM.render( <ApolloProvider client={client}> <App /> </ApolloProvider>, document.getElementById("root") ); ...
-
Update the
src/component/UserList.tsx
component and use the generateduseUsersQuery
anduseDeleteUserMutation
React hooks.import React from "react"; import { useDeleteUserMutation, UsersDocument, UsersQuery, useUsersQuery, } from "../generated/graphql"; export const UserList = () => { const { data, loading, error } = useUsersQuery(); const [deleteUser] = useDeleteUserMutation({ update(cache, { data }) { if (data?.deleteUser) { const cached = cache.readQuery<UsersQuery>({ query: UsersDocument, }); if (cached) { cache.writeQuery({ query: UsersDocument, data: { users: cached.users.filter( (user) => user.id !== data.deleteUser ), }, }); } } }, }); if (!data || loading) return <h2>Loading...</h2>; if (error) return <h2>Error: {error}</h2>; return ( <ul> {data.users.map((user) => ( <li key={user.id}> {user.name} <button onClick={() => deleteUser({ variables: { id: user.id }, }) } > x </button> </li> ))} </ul> ); };
-
Update the
src/component/AddUser.tsx
component and use the generateduseCreateUserMutation
React hooks.import React, { useState } from "react"; import { useCreateUserMutation, UsersDocument, UsersQuery, } from "../generated/graphql"; export const AddUser = () => { const [name, setName] = useState(""); const [createUser] = useCreateUserMutation({ update(cache, { data }) { if (data) { const cached = cache.readQuery<UsersQuery>({ query: UsersDocument, }); if (cached) { cache.writeQuery({ query: UsersDocument, data: { users: [...cached.users, data.createUser] }, }); } } }, }); return ( <div> <input value={name} onChange={(e) => setName(e.target.value)} /> <button disabled={!name} onClick={() => { createUser({ variables: { input: { name } }, }).then(({ data }) => { if (data?.createUser) { setName(""); } }); }} > Add </button> </div> ); };
-
Install
cross-env
package to enable cross environment variable setup for both window and unix based system.npm install -D cross-env
-
Replace the following line under the
package.json
.From :
"test": "react-scripts test",
To :
"test": "cross-env CI=true react-scripts test --coverage",
-
Update the unit test case
src/App.test.tsx
.import { MockedProvider } from "@apollo/react-testing"; import { render, waitForElement } from "@testing-library/react"; import React from "react"; import App from "./App"; import { UsersDocument } from "./generated/graphql"; const mocks = [ { request: { query: UsersDocument, }, result: { data: { users: [ { __typename: "User", id: "1", name: "John", nickName: null }, ], }, }, }, ]; test("renders UserList", async () => { const { getByText } = render( <MockedProvider mocks={mocks}> <App /> </MockedProvider> ); const element = await waitForElement(() => getByText("John")); expect(element).toBeInTheDocument(); });
-
Run the unit test case.
npm test
You should see the following output:
> [email protected] test /Users/cct125/Workspaces/nest/react-graphql-sample > cross-env CI=true react-scripts test --coverage PASS src/App.test.tsx ✓ renders UserList (78ms) -------------------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | -------------------|----------|----------|----------|----------|-------------------| All files | 24.68 | 9.62 | 22.58 | 24 | | src | 2.33 | 0 | 5.88 | 2.33 | | App.tsx | 100 | 100 | 100 | 100 | | index.tsx | 0 | 0 | 100 | 0 | 9,13,23 | serviceWorker.ts | 0 | 0 | 0 | 0 |... 40,141,143,146 | src/component | 44.44 | 27.78 | 30 | 44 | | AddUser.tsx | 33.33 | 0 | 20 | 33.33 |... 18,29,33,36,37 | UserList.tsx | 53.33 | 41.67 | 40 | 53.85 | 14,15,18,19,22,40 | src/generated | 85.71 | 100 | 75 | 85.71 | | graphql.tsx | 85.71 | 100 | 75 | 85.71 | 181 | -------------------|----------|----------|----------|----------|-------------------| Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 2.489s Ran all test suites.
Start the React client server.
cd react-graphql-sample
npm start
You should see the
UserList
andAddUser
components correctly rendered, and all the CRUD operations should work normally.