
Chào mọi người,
Bài viết này sẽ hướng dẫn cách tạo một ứng dụng React, kết hợp sử dụng React Apollo để tương tác với GraphQL server
1. Giới thiệu
- Reactjs là một thư việc Javascript. Nó cung cấp các giải pháp hữu ích cho lập trình front-end, cho phép bạn tạo các ứng dụng web động và tương tác một cách dễ dàng.
 - Gatsbyjs gộp chung React, GraphQL and Webpack để build một ứng dụng nhanh chóng
 - GraphQL là ngôn ngữ truy vấn các API. Nó cho phép phía client-side chỉ lấy các field cần thiết để xử lý trên Front-end và nhiều tính năng hữu ích khác
 - React-Apollo là một thư viện hỗ trợ sử dụng GraphQL trong ứng dụng React
 
Lưu ý: Bài viết này chỉ tập trung vào phần Front-end, nếu bạn nào đã có sẵn GraphQL Server rồi thì có thể đọc tiếp phần sau. Còn không thì bạn nên tham khảo viết này (TypeORM + GraphQL(Type-graphQL) bằng Typescript cho ứng dụng Nodejs) hoặc ít nhất bạn tải source code trong đó về để start phần server, vì không có server chúng ta không thể gọi các API để test được ^^
2. Yêu cầu hệ thống
- Node.js 12.0 trở lên
 - MacOS, Windows (bao gồm WSL), và Linux cũng được hỗ trợ
 
3. Bắt đầu
3.1 Khởi tạo project
Đầu tiên, bạn gõ lệnh sau để cài gatsby-cli
npm install -g gatsby-cli
Đẻ tạo project mới, bạn gõ lệnh sau
gatsby new my-app
Một folder được sinh ra với tên là my-app . Giờ bạn start project lên để xem nó sẽ có gì nào ^^!
cd my-app
npm run develop
Nếu lúc run mà bạn gặp lỗi này
Error: Cannot find module'gatsby-core-utils'
Nghìa là đang thiếu thư viện gatsby-core-utils bạn chỉ cần gõ lệnh sau để cài đặt nó npm i gatsby-core-utils
Mở http://localhost:8000/ sẽ được như hình bên dưới

3.2 Cấu trúc project
- 
src/pagesfolder. Mỗi React Component được đặt trong folder này sẽ sinh ra một route theo tên file đã tạo. VD:- src/pages/index.tsx sẽ render trang trang home với route là 
/ - Nếu bạn tạo src/pages/about.tsx thì người dùng có thể truy cập vào page 
/about 
Đoạn này nếu bạn nào đã đọc bài Dùng Nextjs(Reactjs) + Sequelize + Typescript để tạo ứng dụng blog thì thấy cơ chế nó giống với Nextjs ^^!
 - src/pages/index.tsx sẽ render trang trang home với route là 
 - 
src/componentsfolder để chứa các React Component - 
gatsby-config.jsfile định nghĩa metadata, plugin và cấu hình chung khác của trang web của bạn - 
gatsby-node.jsCho phép bạn tạo thêm các xử lý trước khi render 1 trang html. Như việc mình hay sử dụngcomponentWillMounttrong React để xử lý trước khi React Component được tạo vậy - 
gatsby-browser.jsdùng để wrap các component sử dụng chung trong ứng dụng, bạn có thể cấu hình Layout ở đây là thuận tiện nhất - 
gatsby-ssr.jscũng như gatsby-browser.js nhưng sẽ run ở phía server 
3.3 Áp dụng Typescript
Mặc định thì gatsby đã hỗ trợ sẵn, nên bạn không cần cài đặt gi thêm ^^!. Còn nếu bạn muốn tuỳ chỉnh các thông số của Typescript thì bạn chỉ cần tạo file tsconfig.json ở root folder là được
3.4 Set up Apollo Client (GraphQL Client)
3.4.1 Cài đặt
Bạn gõ lệnh sau để cài đặt các thư viện của React Apollo
npm i @apollo/[email protected]^3.1.3 apollo-cache-inmemory apollo-client apollo-link-http graphql-tag isomorphic-fetch
3.4.2 Set up Apollo Client
Đầu tiên, bạn cần start GraphQL server của bạn lên trước, nếu chưa có thì bạn tham khảo bài viết này sẽ hướng dẫn bạn tạo GraphQL Server trong 5 phút.
Sau đó, bạn tạo ApolloProvider component src/apolloClient/ApolloProvider.tsx như sau
import React,{ ReactElement }from"react"import{ ApolloProvider }from"@apollo/react-hooks"import{ InMemoryCache }from"apollo-cache-inmemory"import{ ApolloClient }from"apollo-client"import{ createHttpLink }from"apollo-link-http"import fetch from"isomorphic-fetch"const httpLink =createHttpLink({
 uri:"http://localhost:3000/graphql",
 fetch,
 fetchOptions:{
   credentials:"include",},})const cache =newInMemoryCache()const client =newApolloClient({
 link: httpLink,
 cache: cache,})interfaceIProps{
 children: ReactElement
}constApolloProviderWrapper=({ children }: IProps)=>{return(<ApolloProvider client={client}>{React.cloneElement(children)}</ApolloProvider>)}exportdefault ApolloProviderWrapper
createHttpLinkđể cấu hình request gửi lên server.uri: "http://localhost:3000/graphql"đây là endpoint Server GraphQL của bạn. Ở bài viết này, mình sẽ sử dụng Server TypeORM mà mình đã đề cập ở trên. Bạn có thể update lại để xài Server riêng của bạnclientđối tượng này đóng vai trò quan trọng trong ứng dụng có sử dụng Apollo, chúng ta sẽ dùng nó để tương tác hoặc lưu data ở client dưới dạng bộ nhớ cache. Mình sẽ đi sâu hơn ở các phần tiếp theo- Xem thêm tại https://www.apollographql.com/docs/react/api/core/ApolloClient/
 
Sau đó, update lại 2 files gatsby-browser.js và gatsby-ssr.js với nội dung như bên dưới. Mục đích là wrap tất cả component bên trong Apollo Provider vừa tạo ở trên, để có thể sử dụng các dữ liệu global và các method của Apollo
import React from"react"import ApolloProvider from"./src/apolloClient/ApolloProvider"exportconstwrapRootElement=({ element })=>{return<ApolloProvider>{element}</ApolloProvider>}
3.4.3 Lấy danh sách Categories
Bạn tạo file src/apolloClient/useCategory.ts với nội dung như bên dưới để set up câu query. Trong ví dụ này thì mình sẽ gọi getCategories query để lấy danh sách các Category như trong bài hướng dẫn TypeORM + GraphQL(Type-graphQL) bằng Typescript cho ứng dụng Nodejs
import gql from"graphql-tag"exportconstGET_CATEGORIES= gql`
 query GetCategories {
   getCategories {
     id
     code
     name
   }
 }
`
Sao đó, tạo Home component src/components/Home.tsx với nội dung như sau
import React from"react"import{ useQuery }from"@apollo/react-hooks"import{GET_CATEGORIES}from"../apolloClient/useCategory"const Home: React.FC=()=>{const{ data, loading, called, error }=useQuery(GET_CATEGORIES)if(loading ||!called){return<div>Loading...</div>}if(error){return<div>{error.message}</div>}const categories = data.getCategories
 return(<div><ul>{categories.map(category =>(<li key={category.id}><b>Name:</b>{category.name}-<b>Code:</b>{category.code}</li>))}</ul></div>)}exportdefault Home
useQueryhook nhận param là một DocumentNode của GraphQL, cái này thì bạn đã tạo ra bằng hàmgqlở trên. Nó return về cho mình một object, trên ví dụ mình chỉ sử dụng 4 thuộc tính- Xem thêm tại cách sử dung useQuery ở đây
 
Sau đó bạn update lại file src/pages/index.tsx (Nhớ rename file index.jsx mặc định của gatsby sang tsx)
import React from"react"import Home from"../components/Home"constIndexPage=()=><Home />exportdefault IndexPage
Bây giờ thử mở home page lên sẽ hiển thị như hình bên dưới (Bạn nào mới chạy code Backend lần đầu sẽ không có data nhé, nhưng đừng lo giờ mình sẽ tới bước tạo Category)
3.4.4 Tạo Category
Bạn thêm vào file src/apolloClient/useCategory.ts đoạn query sau. Lần này chúng ta sẽ sử dụng mutation nhé.
exportconstCREATE_CATEGORY= gql`
  mutation CreateCate($data: CreateCategoryInput!) {
    createCategory(data: $data) {
      id
      name
      code
    }
  }
`
Sau đó bạn tạo file component src/components/CreateCategory.tsx như bên dưới
import React,{ useState }from"react"import{ useMutation }from"@apollo/react-hooks"import{CREATE_CATEGORY}from"../apolloClient/useCategory"const CreateCategory: React.FC=()=>{const[formData, setFormData]=useState({
    name:"",
    code:"",})const[createCategory,{ loading }]=useMutation(CREATE_CATEGORY)asyncfunctioncreateCategoryHandler(){try{awaitcreateCategory({
        variables:{
          data:{...formData,},},})}catch(e){console.log(e)}}functiononChangeName(e){
    formData.name = e.target.value
    setFormData({...formData })}functiononChangeCode(e){
    formData.code = e.target.value
    setFormData({...formData })}return(<div><div><label>Name</label><input value={formData.name} onChange={onChangeName}/></div><div><label>Code</label><input value={formData.code} onChange={onChangeCode}/></div>{loading ?(<span>Creating......</span>):(<button onClick={createCategoryHandler}>Create Category </button>)}</div>)}exportdefault CreateCategory
- 
useMutationhook return về một array với phần tử đầu tiên là một mutate function. Ở đây mình đặt tên làcreateCategory, để gọi khi nào bạn muốn. Phần tử thứ 2 là một object với nhiều properties, ở đây mình chỉ xàiloading - 
mute function nhận param là một object với nhiều tuỳ chọn tuỳ mục đích sử dụng, trong đó tuỳ chọn
variablessẽ luôn là một object để truyền data từ Client lên Server. Ở đây mình truyền vào object có thuộc tínhdatalà bởi vì ở phần Server mình config nhận request gửi lên luôn là một object có key là data, bạn có thể config lại theo ý bạn (ví dụ nhưvariables: {id: 1}chẳng hạn). Dưới đây là hình chụp code mẫu ở Server
 
Tiếp theo bạn import CreateCategory Component mới tạo vào Home Component sẽ được như hình bên dưới:

Sau khi thử tạo một vài Category thì bạn reload lại page để thấy data mới. Chắc bạn sẽ thắc mắc làm sao để auto refetch lại list ? Mình sẽ đi tới phần tiếp theo ^^!
3.4.5 Auto refetch data
Chúng ta sẽ update lại  src/components/CreateCategory.tsx component như sau:
- 
Import
GET_CATEGORIESimport{GET_CATEGORIES}from"../apolloClient/useCategory" - 
Thêm tuỳ chọn
refetchQueriesvào mute functioncreateCategoryawaitcreateCategory({ variables:{ data:{...formData,},}, refetchQueries:[{ query:GET_CATEGORIES}],}) - 
refetchQueriesnhận ra giá trị là một đối tượng query hoặc một mảng chứa các đối tượng query để báo cho Apollo biết những query cần load lại sau khi thực hiện xong một mutation. Mỗi đối tượng query chứa câu truy vấn để thực thi (như cách sử dụnguseQueryvậy đó ^^!) - 
Xem thêm cách sử dụng useMutation tại đây
 
Bây giờ bạn thử tạo thêm vài Category sẽ thấy dữ liệu được update real time
4. Source code
Đây là full source code sau khi làm xong các bước trên (dành cho những bạn nào muốn run trước learn sau 😄). Sau khi tải về bạn chỉ cần run 2 lệnh sau để cài đặt và chạy ứng dụng
npm i
npm run develop
Enjoy!!
Nguồn: viblo.asia



