React Apollo Mutationをやってみた
React Apolloでデータの取得(query)ができたので更新(Mutation)処理に挑戦してみました。 ちなみに更新(Mutation)ができれば新規追加もできると思われます(サンプルソースより)
事前作業
githubのリポジトリにstarをつけたり、外したりするためにはgithubのtokenのscopes(スコープ)を変更しないといけなかった。 もしscopes(スコープ)を変更せずにstarをつける処理をすると以下のエラーがでます
Uncaught (in promise) Error: Your token has not been granted the required scopes to execute this query. The 'addStar' field requires one of the following scopes: ['public_repo', 'gist'], but your token has only been granted the: [''] scopes. Please modify your token's scopes at: https://github.com/settings/tokens
public_repoにチェックしてUpdateのボタンを押す
実際のソース
クリックすると展開されます(長文なので注意)
import React from 'react' - import {gql, useQuery,useMutation} from '@apollo/client'; + import {gql, useQuery} from '@apollo/client'; import { Buffer } from 'buffer'; import {TailSpin} from '@bit/mhnpd.react-loader-spinner.tail-spin'; // 発行する GraphQL クエリ const GET_REPOSITORIES = gql` query searchRepositories($first: Int,$last: Int,$query: String!,$after:String,$before:String, ){ search(query: $query, type: REPOSITORY, first: $first, last: $last, after:$after,before:$before) { repositoryCount pageInfo{ endCursor hasNextPage hasPreviousPage startCursor } edges { cursor node { ... on Repository { id name url stargazers { totalCount } viewerHasStarred } } } } } `; + export const ADD_STAR = gql` + mutation addStar ($input: AddStarInput!) { + addStar (input: $input) { + starrable { + id + viewerHasStarred + } + } + } + ` + export const REMOVE_STAR = gql` + mutation removeStar ($input: RemoveStarInput!) { + removeStar(input: $input) { + starrable { + id + viewerHasStarred + } + } + } + ` const SearchRepositories = props => { console.log(props.query) // GraphQL のクエリを実行 + const {loading, error, data, fetchMore, refetch} = useQuery(GET_REPOSITORIES, {variables: { first: 10 , last:null, query:props.query, after:null, before:null }, }, {fetchPolicy: "cache-and-network",} ); + //starをつける系の関数 + const [ + addStartRepositories, + { loading: mutationAddStarLoading, error: mutationAddStarError } + ] = useMutation(ADD_STAR, { + onCompleted() { + refetch(); + } + } + ); + const addStart = (nodeid) => { + console.log("addStart start") + console.log(nodeid) + addStartRepositories({ variables: { input: { starrableId: nodeid } } , } ); + console.log("addStart end") + } + //starをはずす系の関数 + const [ + removeStartRepositories, + { loading: mutationRemoveStarLoading, error: mutationRemoveStarError } + ] = useMutation(REMOVE_STAR, { + onCompleted() { + refetch(); + } + } + ); + const removeStar = (nodeid) => { + console.log("removeStar start") + console.log(nodeid) + removeStartRepositories({ variables: { input: { starrableId: nodeid } }, } ); + console.log("removeStar end") + } // クエリ実行中の表示(Loading) if (loading) return + <TailSpin color={"black"} height={150} width={150} />; // エラー発生時(レスポンスがないとき)の表示 if (error) return <p style={{color: 'red'}}>{error.message}</p>; // クエリの結果が返ってきたときの表示 const {repositoryCount} = data.search; const search = data.search const {hasNextPage,hasPreviousPage,endCursor,startCursor} = data.search.pageInfo console.log(hasNextPage) console.log(hasPreviousPage) console.log(Buffer.from(startCursor, 'base64').toString()) console.log(Buffer.from(endCursor, 'base64').toString()) return ( <> <h2>SearchRepositories: {repositoryCount}</h2> + {mutationAddStarLoading && <p>Startを追加中です...</p>} + {mutationAddStarError && <p>Startを追加に失敗しました : もう1度やり直してください</p>} + {mutationRemoveStarLoading && <p>Startを外しています...</p>} + {mutationRemoveStarError && <p>Startを外すのに失敗しました : もう1度やり直してください</p>} { //repository data view search.edges.map(edge => { const node = edge.node return ( <li key={node.id}> <a href={node.url} target="_blank" rel="noopener noreferrer">{node.name}</a> + ☆{node.stargazers.totalCount} + {!node.viewerHasStarred && + ( + <button onClick={() => addStart(node.id)}> + スターをつける + </button> + )} + {node.viewerHasStarred && + ( + <button onClick={() => removeStar(node.id)}> + スターを外す + </button> + )} </li> ) }) } {hasNextPage && ( <button onClick={async() => { console.log("data get start"); await fetchMore({ variables: { first: null , last:10, query:props.query, after: endCursor, before:null }, }, ); console.log("data get end"); }} > More </button> ) } {hasNextPage && ( <button onClick={() => fetchMore({ variables: { first: 10 , last:null, query:props.query, after: endCursor, before:null }, updateQuery: (prevResult, { fetchMoreResult }) => { //単純な次のページのデータの場合 return fetchMoreResult; }, }) } > NextPage </button> ) } {hasPreviousPage && ( <button onClick={() => fetchMore({ variables: { first: null , last:10, query:props.query, after: null, before:startCursor }, updateQuery: (prevResult, { fetchMoreResult }) => { //単純な次のページのデータの場合 return fetchMoreResult; }, }) } > PreviousPage </button> ) } </> ) }; export default SearchRepositories
解説1
☆{node.stargazers.totalCount} {!node.viewerHasStarred && ( <button onClick={() => addStart(node.id)}> スターをつける </button> )} {node.viewerHasStarred && ( <button onClick={() => removeStar(node.id)}> スターを外す </button> )}
viewerHasStarredは表示しているユーザーが対象のリポジトリにスターを付けているのかを指します。 スターがついていない場合はaddStartで、ついている場合はremoveStarの関数を呼ぶボタンを表示しています。
解説2
export const ADD_STAR = gql` mutation addStar ($input: AddStarInput!) { addStar (input: $input) { starrable { id viewerHasStarred } } } `
スターをつけるのはシンプルでIDをしてするだけです
書式としては
addStar(input: AddStarInput!): AddStarPayload Adds a star to a Starrable.
戻り値
clientMutationId: String A unique identifier for the client performing the mutation. starrable: Starrable The starrable.
実際のmutation処理は以下です。
クエリー
mutation addStar($input: AddStarInput!) { addStar(input: $input) { starrable { id viewerHasStarred } } }
変数(variables)
{ "input": { "starrableId": "MDEwOlJlcG9zaXRvcnkxMzc3MjQ0ODA=" } }
結果
{ "data": { "addStar": { "starrable": { "id": "MDEwOlJlcG9zaXRvcnkxMzc3MjQ0ODA=", "viewerHasStarred": true } } } }
Starをはずす処理はaddの部分をremoveに変えるだけです。
ちなみにStarがついている状態でaddStarをしてもエラーにはなりません。
解説3
//starをつける系の関数 const [ addStartRepositories, { loading: mutationAddStarLoading, error: mutationAddStarError } ] = useMutation(ADD_STAR, { onCompleted() { refetch(); } } ); const addStart = (nodeid) => { console.log("addStart start") console.log(nodeid) addStartRepositories({ variables: { input: { starrableId: nodeid } } , } ); console.log("addStart end") }
addStartRepositoriesはgithubのAPIを叩く関数です。
この関数はuseMutationというApolloが提供している関数をもちいて作成してます。
useMutationが実行され成功されるとコールバック関数でonCompletedを呼びだし
refetch()を呼ぶ事で更新した内容を反映したリストを再取得しています。
refetch()はリスト取得のクエリーで作る関数になります
const {loading, error, data, fetchMore, refetch} = useQuery(GET_REPOSITORIES,
addStartはユーザーがスターをつけるボタンを押した時に起動する関数です。
ボタン側に書いてもよかったのですが、個人的に見にくいのでこの形にしました
以下の変数はクエリーと同じでローディング中とエラーの変数です。
{ loading: mutationAddStarLoading, error: mutationAddStarError }
loading: mutationAddStarLoadingは更新中にTrueになります。
error: mutationAddStarErrorはエラーになった場合にTrueになります。
実際の使用例は以下のとおりです。
{mutationAddStarLoading && <p>Startを追加中です...</p>} {mutationAddStarError && <p>Startを追加に失敗しました : もう1度やり直してください</p>}
Starをはずす処理はaddの部分をremoveに変えるだけです。
結果
現時点でいけていないのは2ページ目にいる状態の場合にスターをつけたり、外したりする処理をすると 1ページ目にもどってしまう・・・
refetchに変数を設定しないといけないもよう・・・
あう・・・
refetch (variables?: TVariables) => Promise<ApolloQueryResult> クエリを再フェッチし、オプションで新しい変数を渡すことができる関数
参考URL
https://www.apollographql.com/docs/react/data/mutations/#executing-a-mutation https://codesandbox.io/s/mutations-example-app-final-tjoje?file=/src/index.js:871-879 https://qiita.com/koedamon/items/dae8c865b19281b1aa56