私は初心者です。ググりながら書きました、間違いなどあれば教えていただけるとありがたいです
参考
- Apollo なら爆速で GraphQL サーバーと GraphQL クライアントアプリが作れる - Qiira
- React, Redux, GraphQLを学べるオンライン教材「Full Stack Open 2019」がとても良かった - fortkle blog
私が作成したコードは以下です
GraphQLサーバー作成
import { ApolloServer, UserInputError, gql } from 'apollo-server'
import { IResolvers } from 'graphql-tools'
import uuid from 'uuid/v1'
import { ITodo, FILTER } from '../global'
let filter: FILTER = FILTER.SHOW_ALL
let todos: ITodo[] = [
{ id: '6a679350-2aed-11ea-b2b6-89f5b6bcf823', text: '買い物', completed: false },
{ id: '6a679351-2aed-11ea-b2b6-89f5b6bcf823', text: '映画', completed: true, },
{ id: '6a679352-2aed-11ea-b2b6-89f5b6bcf823', text: 'ゲーム', completed: false, },
]
const typeDefs = gql`
enum FILTER {
SHOW_ALL
SHOW_COMPLETED
SHOW_ACTIVE
}
type Todo {
id: ID!
text: String!
completed: Boolean!
}
type Query {
allTodos: [Todo!]!
}
type Mutation {
addTodo(text: String!): Todo!
toggleCompleted(id: ID!): Todo!
setFilter(filter: FILTER!): FILTER!
}
`
const resolvers: IResolvers = {
Query: {
allTodos: () => {
if (filter === 'SHOW_ALL') {
return todos
}
return todos.filter(t =>
filter === 'SHOW_COMPLETED' ? t.completed : !t.completed
)
},
},
Mutation: {
addTodo: (root, args) => {
const todo = { ...args, id: uuid(), completed: false }
todos = [...todos, todo];
return todo
},
toggleCompleted: (root, args) => {
const todo = todos.find(t => t.id === args.id)
if (!todo) {
throw new UserInputError('id not found', {
invalidArgs: args.id,
})
}
todo.completed = !todo.completed
todos = todos.map(t => t.id === todo.id ? todo : t)
return todo
},
setFilter: (root, args) => {
return filter = args.filter
},
},
}
const server = new ApolloServer({
typeDefs,
resolvers,
})
server.listen().then(({ url }) => {
console.log(`Server ready at ${url}`)
})
以下のように取得、更新メソッドをgraphqlで定義してみました
- allTodos : todo一覧取得
- addTodo : todo取得
- toggleCompleted : todoの完了状態を更新
- setFilter : 一覧取得するときのフィルター
vscodeを使っているのであれば、こちらのプラグインでシンタックスハイライトをつけてくれてみやすかったです。(※それ以外にも便利な機能があるプラグインです)
Before
After
ReactでTodoリスト作成
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import ApolloClient from 'apollo-boost'
import { ApolloProvider } from "@apollo/react-hooks"
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql'
})
ReactDOM.render(
<ApolloProvider client={client} >
<App />
</ApolloProvider>,
document.getElementById('root')
)
import React from 'react';
import { useQuery, useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost'
import AddTodo from "./AddTodo";
import TodoList from "./TodoList";
import Footer from "./Footer";
export const ALL_TODOS = gql`
query {
allTodos {
id
text
completed
}
}
`
const ADD_TODO = gql`
mutation addTodo($text: String!) {
addTodo(text: $text) {
id
text
completed
}
}
`
const SET_FILTER = gql`
mutation setFilter($filter: FILTER!) {
setFilter(filter: $filter)
}
`
const App: React.FC = () => {
const { loading, data } = useQuery(ALL_TODOS)
const [addTodo] = useMutation(ADD_TODO, {
refetchQueries: [{ query: ALL_TODOS }]
})
const [toggleCompleted] = useMutation(SET_FILTER, {
refetchQueries: [{ query: ALL_TODOS }]
})
return (
<>
<AddTodo addTodo={addTodo} />
<TodoList {...{ loading, data }} />
<Footer toggleCompleted={toggleCompleted} />
</>
);
}
export default App;
useQuery
, useMutation
を使って、graphqlのサーバーと通信、通信のための関数を取得しています。
addTodo
を実行した直後に、todo一覧を再び取得したかったので、refetchQueries: [{ query: ALL_TODOS }]
と指定するとうまく、addTodo
実行後 -> allTodos
を実行してくれるようでした
import React, { useState } from "react";
interface IProps {
addTodo: (arg: any) => any;
}
const AddTodo: React.FC<IProps> = ({ addTodo }): JSX.Element => {
const [text, setText] = useState<string>("");
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!text.trim()) return;
addTodo({
variables: { text }
})
setText("");
};
return (
<form onSubmit={handleSubmit}>
<input value={text} onChange={e => setText(e.target.value)} />
<button type="submit">Add Todo</button>
</form>
);
}
export default AddTodo
フォームがサブミットされた時のコールバック関数でaddTodo
を実行し、todoを追加します。
propsの型定義を addTodo: (arg: any) => any;
としたのですが、どう書くのいいのかわかりませんので、詳しい方教えていただけるとありがたいです
import * as React from "react";
import { useApolloClient } from '@apollo/react-hooks'
import { gql } from 'apollo-boost'
import { ITodo } from '../global'
import { ALL_TODOS } from './App'
const TOGGLE_COMPLETED = gql`
mutation toggleCompleted($id: ID!) {
toggleCompleted(id: $id) {
id
text
completed
}
}
`
interface IPorps {
loading: boolean;
data: any;
}
const TodoList: React.FC<IPorps> = ({ loading, data }): JSX.Element => {
const client = useApolloClient()
if (loading) {
return <div>loading...</div>
}
const toggleCompleted = (id: string) => {
client.mutate({
mutation: TOGGLE_COMPLETED,
variables: { id },
refetchQueries: [{ query: ALL_TODOS }],
})
}
return (
<ul>
{data && data.allTodos.map((t: ITodo) =>
<li
key={t.id}
style={{ textDecoration: t.completed ? 'line-through' : 'none' }}
onClick={() => toggleCompleted(t.id)}
>
{t.text}
</li>
)}
</ul>
)
}
export default TodoList
ここでは、const client = useApolloClient()
で取得したclient
でclient.mutate
とし、graphqlのMutation
で定義した部分へ通信しています。
graphqlのQuery
で定義した部分へ通信する場合は、client.query
とする良いようです。
import * as React from "react";
import { FILTER } from '../global'
interface IProps {
toggleCompleted: (arg: any) => any;
}
const Footer: React.FC<IProps> = ({ toggleCompleted }): JSX.Element => {
return (
<>
<span>Show:</span>
<button onClick={() => toggleCompleted({ variables: { filter: FILTER.SHOW_ALL }})}>
ALL
</button>
<button onClick={() => toggleCompleted({ variables: { filter: FILTER.SHOW_COMPLETED }})}>
Active
</button>
<button onClick={() => toggleCompleted({ variables: { filter: FILTER.SHOW_ACTIVE }})}>
Completed
</button>
</>
)
}
export default Footer;
todoの一覧表示するフィルターを変更します
あまりわかってないのですが動かせるものを作ることができました。最後まで見ていただいてありがとうございました