久々の更新。
最近、Next.js × TypeScript × Supabaseで勤怠管理アプリを作成しています。
その中で、すでに登録されている出勤時間を一度出力し、出力した内容をrefで値を取得、さらにほかの箇所に受け渡す仕組みを作ったさいにrefの型定義で躓いたので、備忘録として書き残しておきます。
~省略~
export default function Schedule() {
~省略~
const [activeTime, setActiveTime] = useState<string>('');
const [newTime, setNewTime] = useState<string>('');
const [activeId, setActiveId] = useState<string>('');
const [activeType, setActiveType] = useState<string>('');
const [activeName, setActiveName] = useState<string>('');
const timeRef = useRef<(HTMLInputElement | null)[]>([]);
const idRef = useRef<(HTMLInputElement | null)[]>([]);
const typeRef = useRef<(HTMLInputElement | null)[]>([]);
const nameRef = useRef<(HTMLInputElement | null)[]>([]);
const changeTimeHandler = (index: number) => {
const timeElem = timeRef.current;
timeElem && timeElem[index] ? setActiveTime(timeElem[index]?.value) : false;
const idElem = idRef.current;
idElem && idElem[index] ? setActiveId(idElem[index]?.value) : false;
const typeElem = typeRef.current;
typeElem && typeElem[index] ? setActiveType(typeElem[index]?.value) : false;
const nameElem = nameRef.current;
nameElem && nameElem[index] ? setActiveName(nameElem[index]?.value) : false;
}
const changeTimeUpdate = async () => {
const user = await getCurrentUser();
if (user) {
const { error: statusError } = await supabase
.from('works')
.update({ [activeType]: newTime })
.match({ id: activeId, user_id: user.id, work_date: selectedDate });
if (statusError) { alert(`${activeName}を更新できませんでした。`);
}
}
}
return (
~省略~
<div className="flex flex-wrap gap-2">
<dl className='flex flex-wrap gap-y-2 gap-x-1 w-full'>
{eventsOnSelectedDate.length ?
eventsOnSelectedDate.map((event, index) => {
// イベントタイプに基づいて表示を動的に変更
return (
<React.Fragment key={index}>
<dt className={`w-1/4 grid place-content-center rounded-md text-white bg-[${event.color}]`}>
{event.name}
</dt>
<dd className='w-[calc(50%-8px)]'>
<p className='border border-[#cdd0d4] px-3 flex items-center text-lg w-full h-full rounded-md pointer-events-none'>{event.title}</p>
</dd>
<dd className='w-1/4'>
<button className='btn btn-neutral w-full' onClick={() => { changeTimeHandler(index) }}>変更</button>
<input ref={(el) => timeRef.current[index] = el} type='hidden' defaultValue={event.title} />
<input ref={(el) => idRef.current[index] = el} type='hidden' defaultValue={event.id} />
<input ref={(el) => typeRef.current[index] = el} type='hidden' defaultValue={event.type} />
<input ref={(el) => nameRef.current[index] = el} type='hidden' defaultValue={event.name} />
</dd>
</React.Fragment>
);
})
:
<div className='text-center w-full'>出勤の登録がありません</div>
}
</dl>
~省略~
)
}
実際のコードはこんな感じでした。
この書き方だと、ずっとinputのref={~}の部分で型エラーが出ていて、型定義してる部分もちゃんと推論出来るように書いているはずなんですが・・・。
初期化時に明示的に型を指定したりしても直らず・・・。
まあ、テスト環境だとエラー出てても動作問題ないし、一旦そのままでいいやと思ってvercelにデプロイしたら
見事にデプロイエラー出た/(^o^)\
なのでいろいろ調べて調べて調べて・・・
どうやら、ref属性に関数を渡す場合、(el: HTMLInputElement | null) => voldという型にする必要があるそうで。
refの初期化の型だけではなく、elementの属性側にも型指定をしないといけなかったようです。
// 変更前:
<input ref={(el) => timeRef.current[index] = el} type='hidden' defaultValue={event.title} />
// 変更後:
<input ref={(el: HTMLInputElement | null) => { timeRef.current[index] = el }} type='hidden' defaultValue={event.title} />
という風な形で記述を変更したら直りました!
相変わらず型定義奥が深いというか、React特有のお作法というか、まだまだ分からんことだらけだ・・・。