Build a basic CRUD frontend application with React & GraphQL.
-
Setup an empty project using
Create React Appnpm 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-apptool 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.tsxcomponent.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.tsxcomponent.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.tsxas 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 graphqlfor 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 initThe 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 installAdd 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
HOCandComponentcode generation are disabled.
Create the following GraphQL query and mutation files for the generator.
-
src/graphql/Users.graphqlquery Users { users { id name nickName } }
-
src/graphql/CreateUser.graphqlmutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id name nickName } }
-
src/graphql/DeleteUser.graphqlmutation DeleteUser($id: ID!) { deleteUser(id: $id) }
Start the GraphQL server from previous NodeJS GraphQL Backend Tutorial.
cd nodejs-graphql-sample
npm run start:devPlease make sure the GraphQL server is running on http://localhost:4000/graphql.
Now we can do the actually generation now.
npm run codegenNotes 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.tsxcomponent and use the generateduseUsersQueryanduseDeleteUserMutationReact 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.tsxcomponent and use the generateduseCreateUserMutationReact 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-envpackage 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 testYou should see the following output:
> react-graphql-sample@0.1.0 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 startYou should see the
UserListandAddUsercomponents correctly rendered, and all the CRUD operations should work normally.