個人開発記録　2024/04/27~2024/04/29

# 目的

* とにかくやりきることが一番の目的
* 毎日発信することで、発信する習慣作り
* 個人開発をやりきることで、実績作り


# 開発するもの

X(旧Twitter)のパクリ

# 構成

* バックエンド
    * python
    * django REST framework
* DB
    * postgreSQL
* フロントエンド
    * React

# 本編

## `react-router-dom`をインストール

```bash
npm i -S react-router-dom
```

## `Material UI`をインストール

```bash
npm install @mui/material @emotion/react @emotion/styled
npm install @mui/icons-material
```

## ディレクトリを作成

`bulletproof-react`では`features`配下に機能単位でディレクトリを作成し、
機能ディレクトリ配下にコンポーネントや、ルーティングのディレクトリを作成しているらしい。

```bash
mkdir ./src/features/auth
mkdir ./src/features/auth/components
mkdir ./src/features/auth/routes
```

## 会員登録画面で表示する資材を作成

以下サンプルを丸パクリ。
細かいところはおいおい。

https://github.com/mui/material-ui/tree/v5.15.15/docs/data/material/getting-started/templates/sign-up

```jsx:src/features/auth/components/RegisterForm.jsx
import * as React from 'react';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import CssBaseline from '@mui/material/CssBaseline';
import TextField from '@mui/material/TextField';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import Link from '@mui/material/Link';
import Grid from '@mui/material/Grid';
import Box from '@mui/material/Box';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import Typography from '@mui/material/Typography';
import Container from '@mui/material/Container';
import { createTheme, ThemeProvider } from '@mui/material/styles';

function Copyright(props) {
  return (
    <Typography variant="body2" color="text.secondary" align="center" {...props}>
      {'Copyright © '}
      <Link color="inherit" href="https://mui.com/">
        Your Website
      </Link>{' '}
      {new Date().getFullYear()}
      {'.'}
    </Typography>
  );
}

// TODO remove, this demo shouldn't need to reset the theme.

const defaultTheme = createTheme();

export const RegisterForm = () => {
  const handleSubmit = (event) => {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    console.log({
      email: data.get('email'),
      password: data.get('password'),
    });
  };

  return (
    <ThemeProvider theme={defaultTheme}>
      <Container component="main" maxWidth="xs">
        <CssBaseline />
        <Box
          sx={{
            marginTop: 8,
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center',
          }}
        >
          <Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
            <LockOutlinedIcon />
          </Avatar>
          <Typography component="h1" variant="h5">
            会員登録
          </Typography>
          <Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}>
            <Grid container spacing={2}>
              <Grid item xs={12} sm={6}>
                <TextField
                  autoComplete="given-name"
                  name="firstName"
                  required
                  fullWidth
                  id="firstName"
                  label="First Name"
                  autoFocus
                />
              </Grid>
              <Grid item xs={12} sm={6}>
                <TextField
                  required
                  fullWidth
                  id="lastName"
                  label="Last Name"
                  name="lastName"
                  autoComplete="family-name"
                />
              </Grid>
              <Grid item xs={12}>
                <TextField
                  required
                  fullWidth
                  id="email"
                  label="Email Address"
                  name="email"
                  autoComplete="email"
                />
              </Grid>
              <Grid item xs={12}>
                <TextField
                  required
                  fullWidth
                  name="password"
                  label="Password"
                  type="password"
                  id="password"
                  autoComplete="new-password"
                />
              </Grid>
              <Grid item xs={12}>
                <FormControlLabel
                  control={<Checkbox value="allowExtraEmails" color="primary" />}
                  label="I want to receive inspiration, marketing promotions and updates via email."
                />
              </Grid>
            </Grid>
            <Button
              type="submit"
              fullWidth
              variant="contained"
              sx={{ mt: 3, mb: 2 }}
            >
              会員登録
            </Button>
            <Grid container justifyContent="flex-end">
              <Grid item>
                <Link href="#" variant="body2">
                  Already have an account? Sign in
                </Link>
              </Grid>
            </Grid>
          </Box>
        </Box>
        <Copyright sx={{ mt: 5 }} />
      </Container>
    </ThemeProvider>
  );
}
```

## 会員登録画面をルーティング

### `auth`内のルーティングを定義

`bulletproof-react`では細かい機能単位で、ファイルを作成し、`index.js`で細かい機能単位でパスを付与してルーティングしているみたい。

```jsx:src/features/auth/routes/Register.jsx
import { RegisterForm } from '../components/RegisterForm';

export const Register = () => {
    return (
        <>
            <RegisterForm />
        </>
    );
};
```

```jsx:src/features/auth/routes/index.jsx
import { Route, Routes } from 'react-router-dom';
import { Register } from './Register';

export const AuthRoutes = () => {
  return (
    <Routes>
      <Route path="register" element={<Register />} />
    </Routes>
  );
};
```

### アプリケーション全体に対して、`auth`のルーティングを定義

先ほど定義してた、`AuthRoutes`を全体のルーティング部分で読み込む。

```jsx:src/routes/index.jsx
import { useRoutes } from 'react-router-dom';
import { AuthRoutes } from '../features/auth/routes/index';

export const AppRoutes = () => {
    const publicRoutes = useRoutes([
        {
            path: '/auth/*',
            element: <AuthRoutes />,
        },
    ]);

    return <>{publicRoutes}</>;
};
```

`AppRoutes`を`App.js`で読み込む。

```jsx:src/App.js
import './App.css';
import './routes/index'
import './'
import { AppRoutes } from './routes/index'

function App() {
  return (
    <AppRoutes />
  );
}

export default App;
```

`App`を`index.js`で読み込む。
`BrowserRouter`配下に、`App`を配置しないと、いくらルーティングを書いても何も起こらないので注意！
（ここら辺はフロントエンジニアからは当たり前だろといわれそう。。）
`bulletproof-react`では`provider`で`BrowserRouter`やログイン・未ログインの判定をしていたが、それはおいおい。

```jsx:src/App.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

reportWebVitals();
```

## 画面を表示

`http://localhost:3000/auth/register`にアクセス！
サンプル通りの画面が作成されました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2263720/221fae96-36e4-0502-f106-230f860cc80b.png)


# 感想

`bulletproof-react`のやり方を踏襲しつつも、ほとんどやったことないフロントを作成しているため、進捗が全然ありませんでした。。
小さく泥臭く作ることを忘れてはいけないですね。
あと`Material UI`をうまく活用していけば、いい感じのものがさくっと作れそうなので開発がはかどりそう。
明日はdjangoとreactをつなげる部分をやる！