シェルスクリプトマガジン

香川大学SLPからお届け!(Vol.68掲載)

著者:山下 賢治

初めまして。香川大学 工学研究科 修士1年の山下賢治です。今回は、JavaScriptライブラリ「React」と、API向けのクエリー言語「GraphQL」を用いて、GitHubで公開されているリポジトリの検索Webアプリケーションを作成します。リポジトリの絞り込みには、開発言語とスター数を利用します。

シェルスクリプトマガジン Vol.68は以下のリンク先でご購入できます。

図3 「src/auth.js」ファイルに記述する内容

import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

const httpLink = createHttpLink({
  uri: 'https://api.github.com/graphql',
});

const authLink = setContext(() => {
  const TOKEN = process.env.REACT_APP_TOKEN;
  return {
    headers: {
      Authorization: Bearer ${TOKEN},
    },
  };
});

export const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
});

図4 「src/index.js」ファイルに記述する内容

import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloProvider } from '@apollo/client';
import { client } from './auth';
import RepoInfo from './components/RepoInfo';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(
  <React.StrictMode>
    <ApolloProvider client={client}>
      <RepoInfo />
    </ApolloProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

図5 「src/graphql/index.js」ファイルに記述する内容

import { gql } from '@apollo/client';
export const SEARCH_REPO = gql
  query getData($queryString: String!) {
    search(query: $queryString, type: REPOSITORY, first: 10) {
      nodes {
        ... on Repository {
          databaseId
          nameWithOwner
          openGraphImageUrl
        }
      }
    }
  }
;

図6 「src/components/RepoInfo.jsx」ファイルに記述する内容

import React, {useState, useEffect, useCallback} from 'react';
import {useLazyQuery} from '@apollo/client';
import { SEARCH_REPO } from '../graphql';

const useRepoData = () => {
  const [getData, {loading, error, data}] = useLazyQuery(SEARCH_REPO)
  const [query, setQuery] = useState('')
  const fetchData = useCallback(() => {
    getData({
      variables: {
        queryString: query
      }
    });
  }, [getData, query]);
  useEffect(() => {
    fetchData()
  }, [fetchData])
  return [setQuery, {loading, error, data}]
}
const RepoInfo = () => {
  const [fetchData, {loading, error,data}] = useRepoData()
  const handleOnClick = () => {
    fetchData(language:python stars:>100)
  }
  if (loading) return <p>Loading Repository</p>
  if (error) return <p>Error while searching Repository</p>
  return (
    <>
      <button onClick={handleOnClick}>
        search
      </button>
      {
        data ? data.search.nodes.map(
          repo =>
            <div key={repo.databaseId}>
              <h2>{repo.nameWithOwner}</h2>
              <img src={repo.openGraphImageUrl} alt='repoImage' />
            </div>
        )
          : <></>
      }
    </>
  )
}
export default RepoInfo;

図9 変更後の「src/components/RepoInfo.jsx」ファイルの内容

import React, {useState, useEffect, useCallback} from 'react';
import {useLazyQuery} from '@apollo/client';
import { SEARCH_REPO } from '../graphql';

const useInput = initialValue => {
  const [value, set] = useState(initialValue)
  return {value, onChange: (e) => set(e.target.value)}
}
const useRepoData = () => {
  const [getData, {loading, error, data}] = useLazyQuery(SEARCH_REPO)
  const [query, setQuery] = useState('')
  const fetchData = useCallback(() => {
    getData({
      variables: {
        queryString: query
      }
    });
  }, [getData, query]);
  useEffect(() => {
    fetchData()
  }, [fetchData])
  return [setQuery, {loading, error, data}]
}
const RepoInfo = () => {
  const stars = useInput(0)
  const language = useInput('')
  const [fetchData, {loading, error,data}] = useRepoData()
  const handleOnClick = () => {
    fetchData(language:${language.value} stars:>${stars.value})
  }
  if (loading) return <p>Loading Repository</p>
  if (error) return <p>Error while searching Repository</p>
 return (
    <>
      <label>
        Language :
        <input type='text' {...language} />
      </label>
      <label>
        More than :
        <input type='text' {...stars} />
        Stars
      </label>
      <button onClick={handleOnClick}>
        search
      </button>
      {
        data ? data.search.nodes.map(
          repo =>
            <div key={repo.databaseId}>
              <h2>{repo.nameWithOwner}</h2>
              <img src={repo.openGraphImageUrl} alt='repoImage' />
            </div>
        )
          : <></>
      }
    </>
  )
}
export default RepoInfo;