Chapter 12

おまけ:フロントエンドの解説

Eringi_V3
Eringi_V3
2021.09.20に更新
このチャプターの目次

今回、フロントエンドのアプリケーションは出来上がっているものを使いましたが、おまけとしてフロントエンドの簡単な解説も加えてみます。

フロントエンドのデータフェッチにはurqlを使用しました。
https://formidable.com/open-source/urql/

以下の記事が日本語でわかりやすく特徴を解説してくれています。
https://zenn.dev/adwd/articles/f4c5c5120467bb

個人的には、Mutationを行ったあとに__typenameを見て自動でキャッシュを更新してくれるのがよさそうで、今回採用してみました。
使った感想としては、思ってたとおり本当に楽で、小さめのアプリケーションならサーバーの状態管理は全部urqlにおまかせしてしまっていいかなという気持ちになりました。

例えば、Todoの追加を行うときがあります。

addTodo__typenameTodoです。
このMutationが実行されると、__typenameTodoをもつクエリが再実行されます。
今回のアプリケーションの例ではTodo一覧のフェッチが再実行されるといった感じです。

開発者が自身でキャッシュの管理を行う必要がなくなるので、実装はお手軽になりそうです。

また、デフォルトのヘッダーにトークンを設定したい場合は、ちょっと複雑ですがExchangeという仕組みを利用することで設定できます。
https://formidable.com/open-source/urql/docs/architecture/

src/components/AuthorizedUrqlProvider.tsx
import { useAuth0 } from '@auth0/auth0-react';
import React from 'react';
import {
  cacheExchange,
  createClient,
  dedupExchange,
  Exchange,
  fetchExchange,
  Operation,
  Provider,
} from 'urql';
import { fromPromise, fromValue, map, mergeMap, pipe } from 'wonka';
import { API_HOST } from '../config/constants';

const AuthorizedUrqlProvider: React.FC = ({ children }) => {
  const { getAccessTokenSilently } = useAuth0();

  const fetchOptionsExchange =
    (fn: any): Exchange =>
    ({ forward }) =>
    (ops$) => {
      return pipe(
        ops$,
        mergeMap((operation: Operation) => {
          const result = fn(operation.context.fetchOptions);
          return pipe(
            (typeof result.then === 'function'
              ? fromPromise(result)
              : fromValue(result)) as any,
            map((fetchOptions: RequestInit | (() => RequestInit)) => ({
              ...operation,
              context: { ...operation.context, fetchOptions },
            }))
          );
        }),
        forward
      );
    };

  const client = createClient({
    url: API_HOST,
    exchanges: [
      dedupExchange,
      cacheExchange,
      fetchOptionsExchange(async (fetchOptions: any) => {
        const token = await getAccessTokenSilently();

        return Promise.resolve({
          ...fetchOptions,
          headers: {
            Authorization: token ? `Bearer ${token}` : '',
          },
        });
      }),
      fetchExchange,
    ],
  });

  return <Provider value={client}>{children}</Provider>;
};

export default AuthorizedUrqlProvider;
src/main.tsx
import { Auth0Provider } from '@auth0/auth0-react';
import { ChakraProvider } from '@chakra-ui/react';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import AuthorizedUrqlProvider from './components/AuthorizedUrqlProvider';
import {
  AUTH0_AUDIENCE,
  AUTH0_CLIENT_ID,
  AUTH0_DOMAIN,
  AUTH0_REDIRECT_URI,
} from './config/constants';

ReactDOM.render(
  <React.StrictMode>
    <Auth0Provider
      domain={AUTH0_DOMAIN}
      clientId={AUTH0_CLIENT_ID}
      redirectUri={AUTH0_REDIRECT_URI}
      audience={AUTH0_AUDIENCE}
    >
      <ChakraProvider>
        <AuthorizedUrqlProvider>
          <App />
        </AuthorizedUrqlProvider>
      </ChakraProvider>
    </Auth0Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

参考

https://community.auth0.com/t/auth0-react-how-to-use-getaccesstokensilently-for-my-urql-auth-exchange/59402