MUIのBoxコンポーネントのCSS GridとGridコンポーネントのMUIグリッドの違い
Dashboardでは、
<Box gridColumn={`span ${gridSize}`}>
を使っていました。
これは CSS Grid を利用したレイアウトです。
一方、
<Grid item xs={12}>
は Material UIのGridコンポーネントを利用したレイアウトです。
どちらも「横幅を何分使うか」を指定する考え方ですが、
Grid item xs={12} は MUI Grid(12列グリッドシステム)
gridColumn="span 12" は CSS Grid
という違いがあります。 どちらもよく使われますが、仕組みは異なります。
JWT認証のRefreshToken (リフレッシュトークン)とは?
リフレッシュトークン(Refresh Token)
期限切れになったアクセストークンを再発行するための「更新用のトークン」です。
JWT認証では、この考え方がとても重要です。
なぜ必要なの?
例えば、ログインすると
ログイン
│
▼
アクセストークン発行
このアクセストークンには有効期限があります。
例えば
有効期限:30分
30分経つと
401 Unauthorized
になってしまいます。
毎回ログインさせるのは不便
もしリフレッシュトークンがなければ、
30分経過 ↓ 期限切れ ↓ もう一度ID・パスワード入力
になります。
ユーザーからするとかなり面倒です。
リフレッシュトークンがある場合
ログイン時に
ログイン ↓ Access Token(30分) Refresh Token(30日)
の2つが発行されます。
イメージは
ログイン
│
├── Access Token
│ ↑ API呼び出しに使う
│
└── Refresh Token
↑ 更新専用
期限が切れたら
30分後
Access Token ↓ 期限切れ
でも
Refresh Token ↓ 新しいAccess Token発行
できます。
つまり
Access Token①
│
▼
期限切れ
│
Refresh Token
│
▼
Access Token②
になります。
ユーザーは再ログイン不要です。
イメージ
ログイン
│
▼
┌──────────────────────┐
│ Access Token │
│ APIを呼ぶための鍵 │
│ 有効期限30分 │
└──────────────────────┘
┌──────────────────────┐
│ Refresh Token │
│ 鍵を作り直すための鍵 │
│ 有効期限30日 │
└──────────────────────┘
検索APIの構成
今の流れ
- Controller
$service->handle($request)
↓
- PaginationService
$request->condition() $request->sort() $request->direction() $request->limit()
↓
- Repository
paginateByCondition(...)
↓
- queryByCondition()
$condition->q()
で検索。
- fb593cc (HEAD -> main, origin/main) add: データベース操作まわりのファイル追加
- ef4b7ac add: Domain/IndustryRepository, Infrastructure/IndustryRepository, Industry Model の追加
- b243359 add: Industry/SearchRequest, IndustrySearchDto, IndustrySearchConditionを追加
- ceaffc2 add: IndustryControllerの追加
LaravelのDIとは
LaravelのDIとは、 「Controllerの引数に書くだけで、必要なクラスを自動生成して渡してくれる仕組み」
■ ■ Laravel内部で起きていること
リクエストが来ると👇
- FaqControllerが必要
- index()を呼ぶ
- 引数を見て必要なクラスを調べる
- PaginationServiceが必要と判断
- new PaginationService() する
- メソッドに渡す ■ ■ つまり 「自動で new して、引数に入れてくれる」 ■ ■ なんでできるのか
Laravelには「コンテナ」があるからです👇
Service Container(DIコンテナ)
JavaScriptでいうtruthy / falsyとは?
!!project.isFavorite
を理解するには:
- truthy
- falsy
- !
- !!
を順番に理解するとわかります。
■ まず truthy / falsy とは?
JavaScriptでは:
「true / false ではない値」も、条件式で true/false 扱いされる
という特徴があります。
■ truthy
条件式で:true扱いされる値
■ 例
'hello'
123
[]
{}
true
■ if に入れると
if ('hello') {
console.log('実行')
}
↓
実行される
■ falsy
条件式で:false扱いされる値
■ JavaScriptの falsy 一覧(超重要)
false 0 '' null undefined NaN
■ 例
if (0) {
console.log('実行')
}
↓
実行されない
■ ! の意味
否定(NOT)
■ 例
!true
↓
false
■ 次に「!!」
ここが本題。
■ !! の意味
Boolean化
■ やってること
!!value
=
!(!value)
■ 例
!!'hello'
■ 手順 ①
!'hello'
↓
false
②
!false
↓
true
■ 結果
!!'hello' === true
■ なぜ !! つける人いる?
「確実に boolean にしたい」
■ よくある例
const isLoggedIn = !!user
■ user がnullなら
false
■ user object なら
true
useStateフックがわからなくなったら
「お気に入り状態を React の state として管理している」
const [favorite, setFavorite] = useState<boolean>(project.isFavorite)
- favorite → 現在のお気に入り状態
- setFavorite → お気に入り状態を変更する関数
state とは?
簡単に言うと:
画面用の変数
普通の変数との違い
- 普通の変数
let favorite = true
変更しても:
画面更新されない
- useState
const [favorite, setFavorite] = useState(true)
変更すると:
React再レンダリング
useAuthの責務の分離(肥大化問題)
問題となっているuseAuthフック
結論
useAuthは「認証ドメイン全部」を抱えすぎていて、本来は分割すべき状態
import { useEffect } from 'react'
import axios from 'axios'
import { useRouter } from 'next/router'
import { setCookie, destroyCookie, parseCookies } from 'nookies'
import useRequest, { fetcher } from './useRequest'
import type { AxiosError, AxiosResponse } from 'axios'
import { AUTH_TOKEN_KEY } from '@/constant'
import { APIRequest, HTTPMethod } from '@/lib/api/APIRequest'
import { User, UserAndToken, UserDetail, UserExperience, UserPreference } from '@/models'
interface Referral {
redirect?: string
}
export default function useAuth({ redirectTo = '', redirectIfFound = false } = {}) {
const {
data: user,
mutate,
error,
} = useRequest<User>({ url: '/v1/me' } as APIRequest, {
onError: (error: any) => {
if (error.response?.status != 401) return
handleAuthError()
},
})
const router = useRouter()
const loadingUser = user === undefined && !error
useEffect(() => {
if (loadingUser) return
if ((redirectTo && !redirectIfFound && !user) || (redirectIfFound && user)) {
router.push(redirectTo)
}
}, [user, redirectIfFound, redirectTo, loadingUser, router])
const mutateUserDetail = (detail: UserDetail): Promise<User> => mutate({ ...user, detail })
const mutateUserPreference = (preference: UserPreference): Promise<User> =>
mutate({ ...user, preference })
const mutateUserExperience = (experience: UserExperience): Promise<User> =>
mutate({ ...user, experience })
const setToken = (token: string) =>
setCookie(null, AUTH_TOKEN_KEY, token, {
maxAge: 30 * 24 * 60 * 60,
path: '/',
})
const login = async (email, password, rememberMe = false): Promise<User> => {
const response = await fetcher<UserAndToken>({
url: '/v1/login',
method: HTTPMethod.POST,
params: { email, password },
} as APIRequest)
setToken(response.token)
localStorage.setItem('rememberMe', rememberMe ? '1' : '0')
mutate(response.user, { rollbackOnError: true })
return response.user
}
const refresh = async (): Promise<void> => {
try {
const response = await fetcher<UserAndToken>({
url: `/v1/refresh`,
method: HTTPMethod.POST,
})
setToken(response.token)
const user: User = await fetcher<User>({
url: '/v1/me',
method: HTTPMethod.GET,
})
mutate(user, { rollbackOnError: true })
} catch (e) {
console.error(e)
logout(false)
}
}
const register = async (email, password, passwordConfirmation): Promise<User> => {
const registerVia = router.asPath
const response = await fetcher<UserAndToken>({
url: '/v1/register',
method: HTTPMethod.POST,
params: { email, password, passwordConfirmation, registerVia },
} as APIRequest)
setToken(response.token)
mutate(response.user, { rollbackOnError: true })
return response.user
}
const logout = async (needRequest = true) => {
if (location.href.includes('login')) return
if (location.href.includes('register')) return
if (location.href.includes('apply/step1')) return
if (location.href.includes('landing')) return
const cookies = parseCookies()
if (!cookies[AUTH_TOKEN_KEY]) return
needRequest &&
(await fetcher<User>({
url: '/v1/logout',
method: HTTPMethod.DELETE,
} as APIRequest))
destroyCookie(null, AUTH_TOKEN_KEY)
mutate(null)
localStorage.removeItem('rememberMe')
}
const leave = async () => {
await fetcher({
url: '/v1/user/leave',
method: HTTPMethod.DELETE,
} as APIRequest)
destroyCookie(null, AUTH_TOKEN_KEY)
mutate(null)
localStorage.removeItem('rememberMe')
}
const forgotPassword = async (email): Promise<void> => {
const response = await fetcher<User>({
url: '/v1/forgot-password',
method: HTTPMethod.POST,
params: { email },
} as APIRequest)
}
const resetPassword = async (email, password, passwordConfirmation): Promise<void> => {
const token = router.query.token
const response = await fetcher<UserAndToken>({
url: `/v1/reset-password/${token}`,
method: HTTPMethod.POST,
params: { token, email, password, passwordConfirmation },
} as APIRequest)
}
const setReferral = async (referral: string): Promise<string | null> => {
if (!!user) {
return null
}
const respnse = await fetcher<Referral>({
url: `/v1/set-referral/${referral}`,
method: HTTPMethod.POST,
} as APIRequest)
return respnse.redirect ?? '/'
}
const handleAuthError = async () => {
const rememberMe = JSON.parse(localStorage.getItem('rememberMe') ?? '0')
!!rememberMe && (await refresh())
!rememberMe && (await logout(false))
}
const onResponse = (response: AxiosResponse): AxiosResponse => response
const onResponseError = (error: AxiosError): Promise<AxiosError> | undefined => {
if (error.response?.status == 401 && error.config.method !== 'get') {
handleAuthError()
return
}
return Promise.reject(error)
}
axios.interceptors.response.use(onResponse, onResponseError)
return {
loadingUser,
user,
mutateUserDetail,
mutateUserPreference,
mutateUserExperience,
login,
register,
logout,
leave,
forgotPassword,
resetPassword,
setReferral,
}
}