コード譜投稿アプリを作る #2（chakra-uiインストールからレイアウトコンポーネント実装まで）

[←前回の記事](https://qiita.com/t_sekky/items/e60e5346626df4e49a47)

# chakra-uiのインストール
[chakraの公式サイト](https://chakra-ui.com/getting-started)にあるコマンドを叩く
```
% yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion
```

後々iconsも使うことになったので、これもインストールしときます
```
% yarn add @chakra-ui/icons
```

# なんかコンパイルエラー出た
`Could not find a declaration file for module ‘lodash’`というエラーが出た。
lodashの型が必要らしい

以下のコマンドでlodashの型をインストールします。
```
% yarn add @types/lodash
```

`tsconfig.json`内の`conpilerOptions.types`を以下のように変更。
```diff_json:tsconfig.json
{
  "compilerOptions": {
    ...
-   "incremental": true
+   "incremental": true,
+   "types" : ["node", "lodash"]
  },
}
```

# chakra使えてるか確認
chakraを使うには使いたい要素を`ChakraProvider`タグで囲む必要がある。
`_app.tsx`を以下のように編集。
```tsx:/pages/_app.tsx
import type { AppProps } from 'next/app'
import { ChakraProvider } from '@chakra-ui/react'

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>  
  )
}

export default MyApp
```

これでchakra使えるはずなので、トップページを編集します。
```tsx:/pages/index.tsx
import { Text } from '@chakra-ui/react'
import type { NextPage } from 'next'


const Home: NextPage = () => {
  return (
    <Text color='red'>hello world</Text>
  )
}

export default Home
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/664809/ce58183a-f393-0baa-8466-4100cd00786f.png)
いけてそう！


# レイアウトコンポーネントで、レイアウトを共通化する
ヘッダーやフッターなどを各ページで共通化したいので、[Next.jsのドキュメント](https://nextjs.org/docs/basic-features/layouts)に従ってやっていきます。
ヘッダーはサンプルを参考にして以下のように仮作成しました。(Footerはとりあえず空の要素を返すだけにしてます)
```tsx:/components/common/Layout/Header/index.tsx
import React from "react";
import {
  Box,
  Stack,
  Heading,
  Flex,
  Text,
  Button,
  useDisclosure
} from "@chakra-ui/react";
import { HamburgerIcon } from "@chakra-ui/icons";

export const Header = () => {
  const { isOpen, onOpen, onClose } = useDisclosure();
  const handleToggle = () => (isOpen ? onClose() : onOpen());

  return (
    <Flex
      as="nav"
      align="center"
      justify="space-between"
      wrap="wrap"
      padding={6}
      bg="teal.500"
      color="white"
    >
      <Flex align="center" mr={5}>
        <Heading as="h1" size="lg" letterSpacing={"tighter"}>
          Chakra UI
        </Heading>
      </Flex>

      <Box display={{ base: "block", md: "none" }} onClick={handleToggle}>
        <HamburgerIcon />
      </Box>

      <Stack
        direction={{ base: "column", md: "row" }}
        display={{ base: isOpen ? "block" : "none", md: "flex" }}
        width={{ base: "full", md: "auto" }}
        alignItems="center"
        flexGrow={1}
        mt={{ base: 4, md: 0 }}
      >
        <Text>Docs</Text>
        <Text>Examples</Text>
        <Text>Blog</Text>
      </Stack>

      <Box
        display={{ base: isOpen ? "block" : "none", md: "block" }}
        mt={{ base: 4, md: 0 }}
      >
        <Button
          variant="outline"
          _hover={{ bg: "teal.700", borderColor: "teal.700" }}
        >
          Create account
        </Button>
      </Box>
    </Flex>
  );
};
```

共通化したいレイアウトは以下のようなものです。
```tsx:/component/common/Layout/index.tsx
import { Footer } from './Footer'
import { FC, ReactNode } from 'react'
import { Header } from './Header'

type Props = {
  children: ReactNode
}

export const Layout: FC<Props> = ({ children }) => {
  return (
    <>
      <Header />
        <main>{children}</main>
      <Footer />
    </>
  )
}
```

この`Layout`コンポーネントでメインとなる要素を囲めば、ヘッダーとフッターが表示されるという寸法です。
レイアウトの実装には２通りあり、全ページ共通のレイアウトを`_app.tsx`で設定するやり方と、各ページの`index.tsx`から`getLayout`メソッドを呼んでレイアウトを使うか使わないか、もしくはどのレイアウトを使うのかを指定するやり方があるそうです。
ゆくゆくはadminページも作れればいいなと思っているので、今回はadmin用ページとユーザー用ページでレイアウトを出し分けられるように後者のやり方を参考にします。

`app.tsx`を以下のように編集。

```tsx:/pages/_app.tsx
import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'
import { ChakraProvider } from '@chakra-ui/react'

export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  getLayout?: (page: ReactElement) => ReactNode
}

type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout
}

export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  const getLayout = Component.getLayout ?? ((page) => page)

  return (
    <ChakraProvider>
      {getLayout(<Component {...pageProps} />)}
    </ChakraProvider>
  )
}
```

これで例えばトップページで先ほど作ったLayoutコンポーネントを使用したい場合は以下のようにします。
```tsx:/pages/index.tsx
import { Box } from '@chakra-ui/react'
import type { ReactElement } from 'react'
import { Layout } from '../components/common/Layout'
import type { NextPageWithLayout } from './_app'

const Page: NextPageWithLayout = () => {
  return <Box>hello world</Box>
}

Page.getLayout = function getLayout(page: ReactElement) {
  return (
    <Layout>
      {page}
    </Layout>
  )
}

export default Page
```

レイアウトを使用せず、メインコンテンツをそのまま表示したい場合は`return`内を
```tsx
return page
```
のみにし、
別のレイアウトを使いたい場合は
```tsx
return (
  <AnotherLayout>
    {page}
  </AnotherLayout>
)
```
のようにすればよさそうです。
またこのNext.jsのレイアウト機能を使うと、ページを遷移してもレイアウト部分のstateを保持してくれるらしいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/664809/17b0bcd8-3068-b72c-383a-5ba2dd2d4f81.png)

おkおk。


# 参考にしたサイト
- [Next.js公式](https://nextjs.org/)
- [chakra公式](https://chakra-ui.com/)
- [Vue.js + TypeScriptでlodashをインストールしたのにビルド時に「Could not find a declaration file for module ‘lodash’」というエラーとなった](https://sebenkyo.com/2019/09/22/post-0-29/)
- [Next.jsのLayouts](https://zenn.dev/hisho/articles/fe9f4ec4a8e691)