LoginSignup
15
9

More than 3 years have passed since last update.

ReactとApolloでGraphQLを使った簡易Todoリストを作成してみた

Posted at

私は初心者です。ググりながら書きました、間違いなどあれば教えていただけるとありがたいです :bow:

参考


7WoZtWz864.gif


私が作成したコードは以下です

GraphQLサーバー作成

src/server/index.ts
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

Screen Shot 2019-12-31 at 1.44.57.png

After

Screen Shot 2019-12-31 at 1.44.34.png

ReactでTodoリスト作成

src/index.tsx
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')
)
src/components/App.tsx
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 を実行してくれるようでした


src/components/AddTodo.tsx
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; としたのですが、どう書くのいいのかわかりませんので、詳しい方教えていただけるとありがたいです :bow:


src/components/TodoList.tsx
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() で取得したclientclient.mutateとし、graphqlのMutationで定義した部分へ通信しています。

graphqlのQueryで定義した部分へ通信する場合は、client.query とする良いようです。


src/components/Footer.tsx
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の一覧表示するフィルターを変更します


あまりわかってないのですが動かせるものを作ることができました。最後まで見ていただいてありがとうございました :bow:

15
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
9