Phần 1 mình giúp các bạn tạo 1 server nodejs express tạo server graphQL kết hợp mongo rồi. Bây giờ chúng ta sẽ connect với server đã có bằng react.

Cách kết nối client react với graphQL nodejs

2022-03-12 713 lượt xem

Nếu bạn chưa biết viết server graphQL thì bạn xem lại phần 1 của mình nhé: https://ebudezain.com/simple-graphql-api-server-voi-nodejs-va-express

Giới thiệu ban đầu

Đầu tiên bạn phải tạo 1 quả react app với npx như sau:

npx create-react-app <Tên_Folder>
# ví dụ:
npx create-react-app client

Sau đó chúng ta cần truy cập vào bên trong react và sử dụng các package cần thiết để chạy:

# vào react
cd client
# download các package cần thiết như sau:
npm i @apollo/client graphql 

Trước khi sử dụng thì mình giải thích xíu:

Apollo Client là gì?

Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL.

Tạm hiểu Apollo client là 1 thư viện mà nơi đó nó sẽ quản lý state cho chúng ta. Tạm hiểu nó giống với redux hoặc context api vậy đó.

IV. Phần 4 - Client connect để lấy data demo

Connection

Sửa lại file index.js để khởi tạo apollo/client connect đến server. 

import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client'

// nhớ sửa lại đường dẫn của các bạn đang dùng
const GRAPHQL_SERVER_URL = 'http://localhost:5000/graphql'
const client = new ApolloClient({
  uri: GRAPHQL_SERVER_URL,
  cache: new InMemoryCache()
})

sau đó wrapper cái App component lại như này:

<ApolloProvider client={client}>
      <App />
   </ApolloProvider>

Kể từ đây nếu có kết nối thành công thì bạn hoàn toàn có thể truy vấn data mọi lúc mọi nơi thông qua Provider đã wrapper cho cái App.

Query data với useQuery

Sau khi các thiết lập đã thành công thì bạn hoàn toàn có thể sử dụng hook useQuery để get dữ liệu từ server đổ về:

import React from 'react'
import { useQuery, gql } from '@apollo/client';

const AUTHORS = gql`
  query getAuthor {
    authors{
      id,
      name
    }
  }
`;
export default function App() {
  const { loading, error, data } = useQuery(AUTHORS)
  if (loading) return <>Đang load Author</>
  if (error) return <>Load bị lỗi</>
  console.log(data)
  const { authors } = data
  return (
    authors.map(author => <div key={author.id}>{author.name}</div>)
  )
}

Kết quả:

Mình sẽ xây dựng tính năng khi onclick thì sẽ hiện phần chi tiết ra 1 card box riêng (Mình sẽ bõ qua css để bạn dễ hình dung hơn). Chỉnh 3 file như sau:

// app.js
import React, { useState } from 'react'
import { useQuery, gql } from '@apollo/client';
import Authors from './Author';
import Detail from './Author/detail';

const AUTHORS = gql`
  query getAuthor {
    authors{
      id,
      name
    }
  }
`;
export default function App() {
  // tạo 1 state để chứa item author mà được click => hiện thị ra 1 bên 
  const [authorSelected, setAuthorSelected] = useState(null)
  const { loading, error, data } = useQuery(AUTHORS)
  if (loading) return <>Đang load Author</>
  if (error) return <>Load bị lỗi</>
  const { authors } = data
  return <>
    <Authors authors={authors} parentSelectAuthor={setAuthorSelected} />
    <Detail authorID={authorSelected} />
  </>
}

File Authors như sau:

// file Author.js
import React from 'react'
import style from "./Authors.module.css"

export default function Authors({ authors, parentSelectAuthor }) {
    if (!authors.length) return <></>
    return (
        <ul className={style.wrapper}>
            {
                authors.map(author => <li key={author.id} onClick={() => parentSelectAuthor(author.id)}>{author.name}</li>)
            }
        </ul>
    )
}

File Author Detail

/// Detail.js
import { gql, useQuery } from '@apollo/client';
import React from 'react'
import style from "./Authors.module.css"

const AUTHOR___DETAIL_GQL = gql`
query AuthorDetail($authorID: ID!) {
    author(id: $authorID) {
        id,
        age,
        name,
        books{
        id,
        name
        }
    }
}
`;

export default function Detail({ authorID }) {

    /// tạo mấy thành phần mặc định của graphQL
    const { loading, error, data } = useQuery(AUTHOR___DETAIL_GQL, {
        variables: { authorID },
        skip: !authorID,
        pollInterval: 500,
    })
    if (!authorID) return null
    console.log(authorID)

    if (loading) return <>Đang load Author Detail</>
    if (error) return <>Load Author Detail bị lỗi</>

    return (
        <div className={style.detail}>
            <>
                <tr>
                    <td> id: </td>
                    <td> {data.author.id} </td>
                </tr>
                <tr>
                    <td> name: </td>
                    <td> {data.author.name} </td>
                </tr>
                <tr>
                    <td> book cùng tác giả: </td>
                    <td> {
                        data.author.books.map(book => <span style={{ display: 'block' }}>
                            {book.name}
                        </span>)
                    } </td>
                </tr>
            </>
        </div>
    )
}

Kết quả

Tham khảo thêm về useQuery ở đây: https://www.apollographql.com/docs/react/v2/data/queries/

CUD với multation

1 ứng dụng phải luôn có cả CRUD nhưng nãy giừo dài dòng mới làm với R mà chưa có CUD. Vậy mọi thao tác CUD đều nằm trong mutation nên bạn học xong  mutation là xong tất cả: 

Vậy thì trong apollo/client cung cấp cho bạn 1 hook là useMutation trả ra 1 mảng phần tử đầu tiên là hàm để thực thi việc call server action mutation đã định nghĩa từ trước. Tham số thứ 2 là trạng thái data.

Ok vào việc luôn là mình tạo 1 cái form nhập liệu cho name và age. Bấm submit 1 phát thì tạo record mới trong server.

giả sử mình có cái form như này: 

import React, { useState } from 'react'

export default function Form() {
    // có bao nhiêu cái input thì tạo bấy nhiêu property trong values
    const [values, setValues] = useState({ name: '', age: 0 })

    const handleChange = event => {
        setValues({
            ...values,
            [event.target.name]: event.target.value
        })
    }

    const handleSubmit = event => {
        event.preventDefault()
        /// reset form giá trị về như ban đầu
        setValues({ name: '', age: 0 })
        const { name, age } = values
    }
    return (
        <div>
            <form onSubmit={handleSubmit}>
                <input value={values.name} placeholder="nhập name" onChange={handleChange} name="name" />
                <input value={values.age} placeholder="nhập tuổi" onChange={handleChange} name="age" />
                <button type='submit'>submit</button>
            </form>
        </div>
    )
}

Mình sẽ thêm hook mutation vào như này: 

const [actionCall, stateMutation] = useMutation(MUTATION_ACTION_AUTHOR)

với MUTATION_ACTION_AUTHOR là gql định nhĩa gọi data thế nào: 

const MUTATION_ACTION_AUTHOR = gql`
#### createAuthor là mình đặt đại 1 cái tên gì đó --- đây là comment
mutation createAuthor(
  $name: String!
  $age: Int!
) {
    ## trong server mình có định nghĩa 1 hàm createAuthor(name: String, age: Int): Author trong schema --- đây là comment
    createAuthor(name: $name, age: $age) {
        id
        name
        age
    }
}
`;

Mình có cập nhật khi submit thì gọi action như sau: 

 actionCall({
            variables: {
                name: name,
                age: parseInt(age) || 0
            }
        })

Khi gọi submit thì stateMutation bắt đầu là nới hứng lấy trạng thái của endpoint nên chúng ta hoàn toàn có thể console ra để thấy được trạng thái chuyển từ loading called và error / data

Vậy mình vào db xem thì
1. Nếu thất bại thì show error 
2. Thấy nó thành công thì done.

Ơ nhưng mà nghe ng ta đồn thằng graphQL này xịn ở chỗ cập nhật data bind ghê lắm mà sao cái list author vẫ cũ. Chả có thay đổi gì (phải reload lại trang thì mới có 😄 )

Ok mình thêm 1 thuộc tính depend vào khi gọi là được ấy mà. 

actionCall({
            variables: {
                name: name,
                age: parseInt(age) || 0
            },
            refetchQueries: [{ query: gql`
            query getAuthor {
              authors{
                id,
                name
              }
            }
            ` }]
        })

Bạn có thể optimize code nhét cái đống gql để get list Author vào 1 chỗ rồi gọi biến số vào 😄 ở đây mình lười trình bày nên để bát nháo vậy đó. Okey, khi bạn định nghĩa như thế thì useQuery sẽ refresh lại component cho bạn làm bạn tưởng là component đó được realtime luôn (thực chất là call action request nha bạn )

Toàn bộ code form như sau: 

import { gql, useMutation } from '@apollo/client'
import React, { useState } from 'react'

const MUTATION_ACTION_AUTHOR = gql`
#### createAuthor là mình đặt đại 1 cái tên gì đó --- đây là comment
mutation createAuthor(
  $name: String!
  $age: Int!
) {
    ## trong server mình có định nghĩa 1 hàm createAuthor(name: String, age: Int): Author trong schema --- đây là comment
    createAuthor(name: $name, age: $age) {
        id
        name
        age
    }
}
`;

const AUTHOR_LIST = gql`
query getAuthor {
  authors{
    id,
    name
  }
}
`
export default function Form() {

    const [actionCall, stateMutation] = useMutation(MUTATION_ACTION_AUTHOR)
    // có bao nhiêu cái input thì tạo bấy nhiêu property trong values
    const [values, setValues] = useState({ name: '', age: 0 })

    const handleChange = event => {
        setValues({
            ...values,
            [event.target.name]: event.target.value
        })
    }

    const handleSubmit = event => {
        event.preventDefault()
        /// reset form giá trị về như ban đầu
        setValues({ name: '', age: 0 })
        const { name, age } = values
        console.log({ name, age }, "{ name, age }")
        actionCall({
            variables: {
                name: name,
                age: parseInt(age) || 0
            },
            refetchQueries: [{ query: AUTHOR_LIST }]
        })
    }
    console.log(stateMutation, "stateMutation")
    return (
        <div>
            <form onSubmit={handleSubmit}>
                <input value={values.name} placeholder="nhập name" onChange={handleChange} name="name" />
                <input value={values.age} placeholder="nhập tuổi" onChange={handleChange} name="age" />
                <button type='submit'>submit</button>
            </form>
        </div>
    )
}

Kết quả:

V. Phần 5- Realtime Updates with Subscriptions

Một vấn đề quan trọng đối với các ứng dụng web ngày nay là tương tác thời gian thực. Để đáp ứng cho vấn đề trên thì graphQL cung cấp khái niệm Subscriptions.

Khi một client subscribes một sự kiện, nó sẽ khởi tạo và giữ kết nối ổn định với máy chủ. Bất cứ khi nào sự kiện cụ thể đó thực sự xảy ra, máy chủ sẽ đẩy dữ liệu tương ứng đến máy khách.