🐥
React × TypeScript でのメモアプリ作成 #7(カスタムフック化・Enterで発火)
作成日:
2022/02/15
3
学習メモ
カスタムフック化
App.tsx
import classes from "../styles/App.module.scss";
import { useState, useRef, FC, useCallback } from "react";
import { InputArea } from "./InputArea";
import { OutputArea } from "./OutputArea";
import { useMemoList } from "../hooks/useMemoList";
export const App: FC = () => {
const inputElement = useRef<HTMLInputElement>(null);
const {outputText, addTodo, deleteTodo} = useMemoList();
/*
* 追加ボタン押下時
*/
const onClickAddButton = () => {
addTodo(inputElement.current?.value);
// フォームに入力した値をリセットする
inputElement.current!.value = "";
}
/*
* 削除ボタン押下時
* @param {number} index リストの何番目が押されたかを示す番号
*/
const onClickDeleteButton = useCallback((index: number) => {
deleteTodo(index);
}, [deleteTodo]);
return(
<div className={classes.container}>
<h1 className={classes.title}>簡単メモアプリ</h1>
<InputArea inputElement={inputElement} onClickAddButton={onClickAddButton} />
<OutputArea outputText={outputText} onClickDeleteButton={onClickDeleteButton} />
</div>
);
};
hooks/useMemoList.ts
import { useCallback, useState } from "react";
export const useMemoList = () => {
const [outputText, setOutputText] = useState<string[] | null>([]);
/*
* リストを追加
*/
const addTodo = useCallback((inputText: any) => {
if (inputText === "") return; // フォームに入力された文字が空白の場合は何もしない
// フォームに入力した値を配列に入れる
const newTexts = [...outputText!, inputText];
setOutputText(newTexts);
}, [outputText]);
/*
* リストを削除
*/
const deleteTodo = useCallback((index: number) => {
// 表示されている配列を取得
const newTexts = [...outputText!];
// 配列内の該当の要素を削除
newTexts.splice(index, 1);
// リストを更新
setOutputText(newTexts);
}, [outputText]);
return { outputText, addTodo, deleteTodo };
};
エンターキーを押したときに追加できるように変更
App.tsx
// …省略
/*
* エンターキー押下時
*/
const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === "Enter" && event.keyCode === 13) {
addTodo(inputElement.current?.value);
// フォームに入力した値をリセットする
inputElement.current!.value = "";
}
}
// …省略
return(
<div className={classes.container}>
<h1 className={classes.title}>簡単メモアプリ</h1>
<InputArea inputElement={inputElement} handleKeyDown={handleKeyDown} onClickAddButton={onClickAddButton} />
<OutputArea outputText={outputText} onClickDeleteButton={onClickDeleteButton} />
</div>
);
// …省略
inputArea.tsx
import classes from "../styles/App.module.scss";
import { FC } from "react";
type Props = {
inputElement: React.RefObject<HTMLInputElement>;
onClickAddButton: () => void;
handleKeyDown: (event: React.KeyboardEvent) => void;
}
export const InputArea: FC<Props> = props => {
const { inputElement, handleKeyDown, onClickAddButton } = props;
return (
<div className={classes.inputArea}>
<input ref={inputElement} onKeyDown={handleKeyDown} className={classes.inputAreaTextArea} type="text" placeholder="メモを入力" />
<button onClick={onClickAddButton} className={classes.inputAreaButton}>追加</button>
</div>
)
}
変換時にイベントが発火してしまう問題への対処
event.keyCode
が非推奨となっていたため、以下を参考に対応
https://github.com/facebook/react/issues/3926#issuecomment-929799564
App.tsx
const [onComposition, setOnComposition] = useState(false);
/*
* キーボードが入力中どうかの判定
* 変換中にイベントが発火してしまう問題への対処
* https://github.com/facebook/react/issues/3926#issuecomment-929799564
*/
const handleComposition = (event: React.CompositionEventHandler<HTMLInputElement> | any) => {
if (event.type === "compositionstart") {
setOnComposition(true);
}
if (event.type === "compositionend") {
setOnComposition(false);
}
}
// …省略
/*
* エンターキー押下時
*/
const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === "Enter" && !onComposition) {
addTodo(inputElement.current?.value);
// フォームに入力した値をリセットする
inputElement.current!.value = "";
}
}
// …省略
return(
<div className={classes.container}>
<h1 className={classes.title}>簡単メモアプリ</h1>
<InputArea inputElement={inputElement} handleKeyDown={handleKeyDown} onClickAddButton={onClickAddButton} handleComposition={handleComposition} />
<OutputArea outputText={outputText} onClickDeleteButton={onClickDeleteButton} />
</div>
);
InputArea.tsx
import classes from "../styles/App.module.scss";
import { FC } from "react";
type Props = {
inputElement: React.RefObject<HTMLInputElement>;
onClickAddButton: () => void;
handleKeyDown: (event: React.KeyboardEvent) => void;
handleComposition:(event: React.CompositionEventHandler<HTMLInputElement> | any) => void;
}
export const InputArea: FC<Props> = props => {
const { inputElement, handleKeyDown, handleComposition, onClickAddButton } = props;
return (
<div className={classes.inputArea}>
<input ref={inputElement} onKeyDown={handleKeyDown} onCompositionStart={handleComposition} onCompositionEnd={handleComposition} className={classes.inputAreaTextArea} type="text" placeholder="メモを入力" />
<button onClick={onClickAddButton} className={classes.inputAreaButton}>追加</button>
</div>
)
}
Safariで確認したらだめだった
全ブラウザに対応したければこちらの
compositionstart
と compositionend
イベントの監視を自作する方法で対処することになりそう
所感
目標までは終わりました。
まだまだ覚えることがたくさんあるので、できることを増やしていきたいです。