連続10日間でサイト作る予定だったのですが、別件で忙しかったため半月程度時間が空いてしまった。しかたがないので合計10日間での開発日記とする!
day6の課題は「解答フォームの作成」です。UIフレームワークはMUIを利用しているため、FormControlなどを使って実装しようかとも考えたのですが、4択選択肢だけの実装となるため愚直に実装した方がシンプルです。
コンポーネントの実装はこんな感じ。
export const Quiz = ({ quiz }: { quiz: Quiz }) => {
const [answer, setAnswer] = useState<number | null>(null)
const { showSnackbar } = useSnackbar()
const handleAnswer = () => {
if (answer == null) {
showSnackbar('warning', 'please select answer.')
return
}
// todo
}
return (
<Box
width="80%"
mx="auto"
my={4}
p={4}
display="flex"
sx={{
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-around',
borderRadius: '10px',
}}
className="SignInDialog"
>
<AppIcon sx={{ fontSize: 80 }} />
<Typography my={3} variant="h6">
第N問
</Typography>
<Typography my={3} variant="h6">
{quiz.question}
</Typography>
<Box flexDirection="column" sx={{ display: 'flex', gap: 2 }}>
{quiz.choices.map((choice, index) => (
<Choice
key={index}
choice={choice}
index={index}
isSelected={answer === index}
onClick={() => setAnswer(index)}
/>
))}
</Box>
<Box width="50%" display="flex" mt={3} sx={{ justifyContent: 'space-around' }}>
<Button variant="outlined" onClick={handleAnswer}>
ANSWER
</Button>
</Box>
</Box>
)
}
type ChoiceProps = {
choice: string
index: number
isSelected: boolean
} & BoxProps
const Choice = ({ choice, index, isSelected, ...props }: ChoiceProps) => {
return (
<Box sx={{ display: 'flex', alignItems: 'start' }} {...props}>
<Typography width={16} flexShrink={0}>
{index + 1}
</Typography>
<Typography color={isSelected ? 'primary' : undefined}>{choice}</Typography>
</Box>
)
}
className=”SignInDialog”
って感じでcssに定義したクラスセレクタを利用しているんですが、cssファイル自体使わない方がベターなのでしょうか?sxやstyle propsにゴリゴリにスタイルを書いていくのがどうも好みじゃない。複雑なデザインを実現しようとすると長文になってコンポーネントの視認性が悪くなるのが特にイヤ。ファイル分離したいような、かといってファイルが別だと参照するのがめんどいような。だれか上手く解決してくれないかなぁ。
Snackbarのcontext化
MUIのSnackbarは使いやすいのですが、setIsOpen的なものを都度書くのは辛い。
私の場合は複数個のSnackbarを同時に表示することはないため、Globalに1つSnackbarを定義し、ContextProviderでSnackbarの表示ロジックを使いまわすことにします。
Snackbar Component
内部にAlertコンポーネントを利用し、severityとmessageを渡すと表示される作りにします。
import { Alert, AlertColor, Snackbar as MuiSnackbar } from '@mui/material'
type Props = {
isOpen: boolean
severity: AlertColor
message: string
onClose: () => void
}
export function Snackbar({ isOpen, severity, message, onClose }: Props) {
return (
<MuiSnackbar
open={isOpen}
autoHideDuration={3000}
onClose={onClose}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
>
<Alert variant="filled" onClose={onClose} severity={severity}>
{message}
</Alert>
</MuiSnackbar>
)
}
Snackbar Context Provider
createContextしてContextProviderにshowSnackbarという関数を持たせます。
この関数を簡単に呼び出せるようにuseSnackbarというhookも定義しておきます。
import { AlertColor } from '@mui/material'
import { createContext, useContext, useState } from 'react'
import { Snackbar } from '../components/Snackbar'
type SnackbarContext = {
showSnackbar: (severity: AlertColor, message: string) => void
}
const SnackbarContext = createContext<SnackbarContext>({} as SnackbarContext)
export const SnackbarProvider = ({ children }: { children: React.ReactNode }) => {
const [isOpen, setIsOpen] = useState(false)
const [severity, setSeverity] = useState<AlertColor>('info')
const [message, setMessage] = useState('')
const showSnackbar = (severity: AlertColor, message: string) => {
setIsOpen(true)
setSeverity(severity)
setMessage(message)
}
const closeSnackbar = () => setIsOpen(false)
return (
<SnackbarContext.Provider value={{ showSnackbar }}>
{children}
<Snackbar isOpen={isOpen} severity={severity} message={message} onClose={closeSnackbar} />
</SnackbarContext.Provider>
)
}
export const useSnackbar = () => useContext(SnackbarContext)
呼び出し側はuseSnackbar()からshowSnackbarを取り出し、重大度とメッセージを渡せばOKです。
const { showSnackbar } = useSnackbar()
showSnackbar('warning', 'message here')
コメント