Contributorsのプロフィールを改善しました

ぱっと見ていたところContributorのプロフィールが気になったので色々調整してみました。

## アイコンがない

下記のようにContributorsのアイコンが歯抜けになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136506/04fe60a0-f27a-1c10-63c6-98478865ecf3.png)

GitHubのリンクが設定されているContributorはAPIでアイコンを取ってきているようなのですが、それ以外のユーザーはアイコンがありません。なので埋めることにしました。

こんな感じでアイコンがない人用の画像を準備しておき

```typescript
export const avatars: string[] = [
  "1447555445.png",
  "1446262260.png",
  "1454668491.png",
  "1478953496.png",
  "1522414407.png",
  "1449057230.png",
  "1479466248.png",
  "1524969274.png",
  "1507063429.png",
  "1522154395.png",
  "1520346636.png",
  "1450533682.png",
  "1461581760.png",
  "1476945442.png",
  "1478445108.png",
  "1503360665.png",
];
```

あとはなんやかんやアイコンがない人の数を調べてアイコンを順に使うようにして…

```typescript
  const getDefaultAvatarUrl = useCallback((contributor: Contributor) => {
    const index = contributors.findIndex((c) => c.slug === contributor.slug);
    const noIconCount = contributors
      .slice(0, index)
      .filter((c) => !getGitHubIconUrl(c)).length;
    const avatarIndex = noIconCount % avatars.length;
    return `/images/avatars/${avatars[avatarIndex]}`;
  }, []);
```

ちゃんと全員表示されるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136506/f0f47386-5986-08ba-2906-22aae3e84662.png)

プロフィール詳細ページではちゃんと大きく表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136506/4e09e4ce-d64b-372f-873d-7027573cce9d.png)

みんなかっこよくなりましたね！！！

GitHubリンクを設定しなければ表示されるのでこのアバターを試したい人はわざと抜いて楽しみにしてみてください。

## アイコンをキャッシュ

上記を実装している途中で気づきましたがアイコンを表示するために毎回GitHubのAPIが呼び出されているのでせめてそのセッション中は何度も呼ばないようキャッシュするようにしておきました。~~ちなみに対応中にuseEffectの無限ループでAPIを呼びすぎてRate Limitにひっかかったのは内緒です~~

まずアイコン用のカスタムフックを作成しました。キャッシュの保存にはRecoilを使っています。

```typescript
const avatarUrlsState = atom<{ [key: string]: string }>({
  key: "contributors/avatarUrls",
  default: {},
});

export const useContributors = () => {
  const [avatarUrls, setAvatarUrls] = useRecoilState(avatarUrlsState);

  const loadAvatarUrl = useCallback(
    async (contributor: Contributor) => {
      if (avatarUrls[contributor.slug]) {
        return;
      }

      const githubUrl = getGitHubIconUrl(contributor);
      if (!githubUrl) {
        setAvatarUrls((avatarUrls) => ({
          ...avatarUrls,
          [contributor.slug]: getDefaultAvatarUrl(contributor),
        }));
        return;
      }

      const user = await getGitHubUser({ githubUrl });
      setAvatarUrls((avatarUrls) => ({
        ...avatarUrls,
        [contributor.slug]: user.avatar_url as string,
      }));
    },
    [Object.keys(avatarUrls).length]
  );

  const getAvatarUrl = useCallback(
    (contributor?: Contributor) => {
      if (!contributor) {
        return undefined;
      }
      return avatarUrls[contributor.slug];
    },
    [Object.keys(avatarUrls).length]
  );

  return { getAvatarUrl, loadAvatarUrl };
};
```

ちなみにみんな思い思いに色々導入しているので_app.tsxはこんな事になっています。~~Chakra UIを入れようかと思いましたがやめときました。~~

```tsx
function MyApp({ Component, pageProps }: Props) {
  const { layout = (page) => page } = Component;
  return (
    <SessionProvider session={pageProps.session}>
      <VFXProvider>
        <Provider store={store}>
          <RecoilRoot>
            <StyletronProvider value={styletron}>
              <BaseProvider theme={LightTheme}>
                <SnowfallLayout>
                  {layout(<Component {...pageProps} />)}
                </SnowfallLayout>
              </BaseProvider>
            </StyletronProvider>
          </RecoilRoot>
        </Provider>
      </VFXProvider>
    </SessionProvider>
  );
}
```

アイコンを表示している箇所はトップの一覧とプロフィール詳細の２箇所ありますので、このカスタムフックを使えばどちらでも同様のキャッシュを利用できるようになりました。全体として各々のユーザーごとに１回までしかAPIを呼ばないようにできました。

## relを調整

下記のようになっていたので

```tsx
<a target="_blank" rel="noreferer noreferrer" href={link.url}>
```

下記に調整しました。

```tsx
  <a
    target="_blank"
    rel="nofollow noreferrer noopener me"
    href={link.url}
  >
```

## まとめ

以上、良いContributionライフを！

↓ マージされたら反映されます。

https://anyway-merge-app-xi.vercel.app/