간단한 Hasura GraphiQL2
이번에는 실제로 그 단점을 CRUD 처리해 보았다.
기술 스택
스노우팩으로 리액트+타입 스크립트+아폴로의 환경을 만들었다.
스노우팩 공식에는 설치에 대한 자세한 내용이 적혀 있다.
다음 템플릿은 React+Type Script에서 사용됩니다.
UI 만들기
index.html
에는 읽기만 해도 기분이 좋은 css가 추가됐다.water.css 좋아해요.
먼저 외관을 추가합니다.
추가 TODO용 input을 controlled component로 만들어야 하기 때문이다.
index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import { App } from './app'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
app.tsximport React, { useState } from 'react'
export const App = () => {
const [todoItemText, setTodoItemText] = useState('')
return (
<div>
<h1>GraphQL Checklist</h1>
{/* Todo form */}
<form>
<input
type='text'
name='todo'
id='todo'
placeholder='Write your todo'
value={todoItemText}
onChange={ev => setTodoItemText(ev.target.value)}
/>
<button type='submit'>Add</button>
</form>
{/* Todo list */}
<ul>
<li>
<span style={{ display: 'inline-block', marginRight: '1em' }}>
todo item 1
</span>
<button type='button'>✓</button>
<button type='button'>×</button>
</li>
</ul>
</div>
)
}
Apollo client 설정
이곳의 절차는 공식 문서를 보는 것이다.
원하는 매크로 패키지를 추가합니다.
yarn add @apollo/client graphql
Apollo client 준비공식은 다음과 같은client 를 정의했다
import { ApolloClient, InMemoryCache } from '@apollo/client'
const client = new ApolloClient({
uri: 'https://48p1r2roz4.sse.codesandbox.io',
cache: new InMemoryCache()
})
api키를 헤더에 추가할 때 다음과 같습니다.import { createHttpLink, ApolloClient, InMemoryCache } from '@apollo/client'
const httpLink = createHttpLink({
uri: 'https://your.hasura.app/v1/graphql',
headers: {
'x-hasura-admin-secret': YOUR_HASURA_ADMIN_SECRET
}
})
export const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache()
})
index.tsx
측으로provide를 진행한다.index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
+ import { ApolloProvider } from '@apollo/client'
+ import { App, client } from './app'
ReactDOM.render(
+ <ApolloProvider client={client}>
+ <React.StrictMode>
+ <App />
+ </React.StrictMode>
+ </ApolloProvider>,
document.getElementById('root')
)
app.tsximport React, { useState } from 'react'
+ import { createHttpLink, ApolloClient, InMemoryCache } from '@apollo/client'
+ const httpLink = createHttpLink({
+ uri: 'https://your.hasura.app/v1/graphql',
+ headers: {
+ 'x-hasura-admin-secret': YOUR_HASURA_ADMIN_SECRET
+ }
+ })
export const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache()
})
export const App = () => {
const [todoItemText, setTodoItemText] = useState('')
return (
<div>
<h1>GraphQL Checklist</h1>
{/* Todo form */}
<form>
<input
type='text'
name='todo'
id='todo'
placeholder='Write your todo'
value={todoItemText}
onChange={ev => setTodoItemText(ev.target.value)}
/>
<button type='submit'>Add</button>
</form>
{/* Todo list */}
<ul>
<li>
<span style={{ display: 'inline-block', marginRight: '1em' }}>
todo item 1
</span>
<button type='button'>✓</button>
<button type='button'>×</button>
</li>
</ul>
</div>
)
}
GraphiQL 쿼리 추가
질의를 정의합니다.
app.tsx
import React, { useState } from 'react'
import {
createHttpLink,
ApolloClient,
InMemoryCache,
+ gql
} from '@apollo/client'
const httpLink = createHttpLink({
uri: 'https://your.hasura.app/v1/graphql',
headers: {
'x-hasura-admin-secret': YOUR_HASURA_ADMIN_SECRET
}
})
export const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache()
})
+ const GET_TODOS = gql`
+ query getTodos {
+ todos {
+ done
+ id
+ text
+ }
+ }
+ `
+ const TOGGLE_TODO = gql`
+ mutation toggleTodo($id: uuid!, $done: Boolean!) {
+ update_todos(where: { id: { _eq: $id } }, _set: { done: $done }) {
+ returning {
+ done
+ id
+ text
+ }
+ }
+ }
+ `
+ const ADD_TODO = gql`
+ mutation addTodo($text: String!) {
+ insert_todos(objects: { text: $text }) {
+ returning {
+ done
+ id
+ text
+ }
+ }
+ }
+ `
+ const DELETE_TODO = gql`
+ mutation deleteTodos($id: uuid!) {
+ delete_todos(where: { id: { _eq: $id } }) {
+ returning {
+ done
+ id
+ text
+ }
+ }
+ }
+ `
export const App = () => {
const [todoItemText, setTodoItemText] = useState('')
return (
<div>
<h1>GraphQL Checklist</h1>
{/* Todo form */}
<form>
<input
type='text'
name='todo'
id='todo'
placeholder='Write your todo'
value={todoItemText}
onChange={ev => setTodoItemText(ev.target.value)}
/>
<button type='submit'>Add</button>
</form>
{/* Todo list */}
<ul>
<li>
<span style={{ display: 'inline-block', marginRight: '1em' }}>
todo item 1
</span>
<button type='button'>✓</button>
<button type='button'>×</button>
</li>
</ul>
</div>
)
}
CRUD 프로세스 추가
Apollo client
useQuery
와 useMutation
에 query와mutation을 각각 기술합니다.추가 처리는 완성형에 쓴 후 다음과 같다.
app.tsx
import React, { useState } from 'react'
import {
createHttpLink,
ApolloClient,
InMemoryCache,
gql,
+ useQuery,
+ useMutation
} from '@apollo/client'
const httpLink = createHttpLink({
uri: 'https://your.hasura.app/v1/graphql',
headers: {
'x-hasura-admin-secret': YOUR_HASURA_ADMIN_SECRET
}
})
export const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache()
})
const GET_TODOS = gql`
query getTodos {
todos {
done
id
text
}
}
`
const TOGGLE_TODO = gql`
mutation toggleTodo($id: uuid!, $done: Boolean!) {
update_todos(where: { id: { _eq: $id } }, _set: { done: $done }) {
returning {
done
id
text
}
}
}
`
const ADD_TODO = gql`
mutation addTodo($text: String!) {
insert_todos(objects: { text: $text }) {
returning {
done
id
text
}
}
}
`
const DELETE_TODO = gql`
mutation deleteTodos($id: uuid!) {
delete_todos(where: { id: { _eq: $id } }) {
returning {
done
id
text
}
}
}
`
+ interface ITodo {
+ done: boolean
+ id: string
+ text: string
+ }
+ interface IGetTodosData {
+ todos: ITodo[]
+ }
export const App = () => {
const [todoItemText, setTodoItemText] = useState('')
+ const { data, loading, error } = useQuery<IGetTodosData>(GET_TODOS)
+ const [toggleTodo] = useMutation(TOGGLE_TODO)
+ const [addTodo] = useMutation(ADD_TODO, {
+ onCompleted: () => setTodoItemText('')
+ })
+ const [deleteTodo] = useMutation(DELETE_TODO)
+ const handleAddTodo = async (ev: React.FormEvent<HTMLFormElement>) => {
+ ev.preventDefault()
+ if (!todoItemText.trim()) return
+ await addTodo({
+ variables: { text: todoItemText },
+ refetchQueries: [{ query: GET_TODOS }]
+ })
+ }
+ const handleToggleTodo = async ({ id, done }: ITodo) => {
+ await toggleTodo({ variables: { id: id, done: !done } })
+ }
+ const handleDeleteTodo = async ({ id }: ITodo) => {
+ if (!window.confirm('Do you want to delete this todo?')) {
+ return
+ }
+ await deleteTodo({
+ variables: { id },
+ update: cache => {
+ const prevData = cache.readQuery<IGetTodosData>({ query: GET_TODOS })
+ if (!prevData) return
+ const newTodos = prevData.todos.filter(todo => todo.id !== id)
+ cache.writeQuery({ query: GET_TODOS, data: { todos: newTodos } })
+ }
+ })
+ }
+ if (loading) return <div>loading...</div>
+ if (error) {
+ console.error(error)
+ return <div>Error fetching todos!</div>
+ }
return (
<div>
<h1>GraphQL Checklist</h1>
{/* Todo form */}
+ <form onSubmit={ev => handleAddTodo(ev)}>
+ <input
type='text'
name='todo'
id='todo'
placeholder='Write your todo'
+ value={todoItemText}
+ onChange={ev => setTodoItemText(ev.target.value)}
/>
<button type='submit'>Add</button>
</form>
{/* Todo list */}
<ul>
+ {typeof data !== 'undefined' &&
+ data.todos.map(todo => (
+ <li key={todo.id}>
+ <span style={{ textDecoration: todo.done ? 'line-through' : '' }}>
+ {todo.text}
+ </span>{' '}
+ <button type='button' onClick={() => handleToggleTodo(todo)}>
+ ✓
+ </button>
+ <button type='button' onClick={() => handleDeleteTodo(todo)}>
+ ×
+ </button>
+ </li>
+ ))}
</ul>
</div>
)
}
완성형의 외관은 다음과 같다.하수라 콘솔의GraphiQL에서는 로컬에서mutation을 실행한 후 DB에 저장된 TODO 일람표를 확인할 수 있다.
중점적 인 실장 을 묘사하고 있다
유형 정의
interface ITodo {
done: boolean
id: string
text: string
}
interface IGetTodosData {
todos: ITodo[]
}
Hasura console에서 테이블을 만들 때 정의된 유형의 에뮬레이션입니다.취득한 TODO 데이터의 유형을 나타냅니다.useQuery API
const GET_TODOS = gql`
query getTodos {
todos {
done
id
text
}
}
`
// 中略
const { data, loading, error } = useQuery<IGetTodosData>(GET_TODOS)
Apollo 응용 프로그램에서query를 실행하는 주요 API 형식은 React hooks입니다.gql
query documents에 있는 의류useQuery
로 기능을 해석하면 data
,loading
,error
를 얻을 수 있다.loading
와error
가false
면query가 완성됩니다.공식 예에서는 GraphiQL variables를 전달하여 클라이언트로부터 변수의 값을 지정할 수도 있습니다.
const GET_DOG_PHOTO = gql`
query Dog($breed: String!) {
dog(breed: $breed) {
id
displayImage
}
}
`
function DogPhoto({ breed }) {
const { loading, error, data } = useQuery(GET_DOG_PHOTO, {
variables: { breed }
})
if (loading) return null
if (error) return `Error! ${error}`
return <img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
}
useMutation API
const TOGGLE_TODO = gql`
mutation toggleTodo($id: uuid!, $done: Boolean!) {
update_todos(where: { id: { _eq: $id } }, _set: { done: $done }) {
returning {
done
id
text
}
}
}
`
const ADD_TODO = gql`
mutation addTodo($text: String!) {
insert_todos(objects: { text: $text }) {
returning {
done
id
text
}
}
}
`
const DELETE_TODO = gql`
mutation deleteTodos($id: uuid!) {
delete_todos(where: { id: { _eq: $id } }) {
returning {
done
id
text
}
}
}
`
// 中略
const [toggleTodo] = useMutation(TOGGLE_TODO)
const [addTodo] = useMutation(ADD_TODO, {
onCompleted: () => setTodoText('')
})
const [deleteTodo] = useMutation(DELETE_TODO)
useQuery
에서 데이터를 얻었습니다.useMutation
에서 데이터를 업데이트할 수 있습니다.useMutation
에서는mutation을 실행하는mutate function과 현재mutation이 실행하는 현재 상태를 표시하는 대상을 얻을 수 있습니다.onCompleted
에서mutation의callback 함수를 지정합니다.Apollo client는query의 결과를 로컬 캐시하는 기구가 있지만 Apollo client의cache는mutation의 결과를 자동으로 업데이트하지 않기 때문에 적절한 처리가 필요합니다.
실행
const handleAddTodo = async (ev: React.FormEvent<HTMLFormElement>) => {
ev.preventDefault()
if (!todoText.trim()) return
await addTodo({
variables: { text: todoText },
refetchQueries: [{ query: GET_TODOS }]
})
}
const handleToggleTodo = async ({ id, done }: ITodo) => {
await toggleTodo({ variables: { id: id, done: !done } })
}
const handleDeleteTodo = async ({ id }: ITodo) => {
if (!window.confirm('Do you want to delete this todo?')) {
return
}
await deleteTodo({
variables: { id },
update: cache => {
const prevData = cache.readQuery<IGetTodosData>({ query: GET_TODOS })
if (!prevData) return
const newTodos = prevData.todos.filter(todo => todo.id !== id)
cache.writeQuery({ query: GET_TODOS, data: { todos: newTodos } })
}
})
}
useMutation
에서 얻은mutate function을 실행합니다.위에서 말한 바와 같이mutation 결과를 다시 캐시해야 하기 때문에
refetchQueries
와update
에서 처리합니다.refetchQueries
에서mutation을 실행한 후에 다시 가져올 검색어를 지정합니다.update
캐시를 업데이트하는 함수를 실행합니다.총결산
이번에 하수라와 리액트+아폴로 클라이언트에서 크루 처리가 이뤄졌다.
Hasura를 사용하고 싶어요.GraphiQL과 그 생태계에 조금 익숙해져요.
Reference
이 문제에 관하여(간단한 Hasura GraphiQL2), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/mrsung/articles/0c27b767060fec텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)