

react のhooksを使ってsetStateを呼びだしたときに、再レンダリングが走りません。  
なぜでしょうか？  
todoアプリのdeleteを押したときに、stateの値は更新されているようですが、再描画が走りません。  
stateには配列を入れて、更新をしているはずなのですが。。  
以下、コードです。

[code]

    // app.txs
    const App = () =>{
    const [todo, setTodo] = React.useState<String[]>([])
    // データ保存
    function handleAdd(e:any){
        e.preventDefault()
        setTodo(todo.concat(e.target.title.value))
        e.target.title.value=""
    }
    // データ削除
    const handleRemove = (i:number)=>{
        // todo 配列のi番目から1つ目のデータを除外
        todo.splice(i,1)
        setTodo(todo)
        console.log(todo)
    }
    return(
        <div>
            <h1>React Todo App</h1>
            <Form handleAdd={handleAdd} />
            <div></div>
            <List todos={todo} handleRemove={handleRemove} />
        </div>
    )
    }
    ReactDOM.render(<App/>, document.getElementById("app"))
    
    
    // List.tsx
    const List = (props: any) =>{
    return (
    <ul>
        {props.todos.map((v:any, i:any) =>{
            return <li key={i}>{v} <span style={{cursor: "pointer"}} onClick={()=>{props.handleRemove(i)}}>delete</span></li>
        })}
    </ul>
    )}
    export default List
    
[/code]


CC BY-SA 4.0
2019-09-03T03:06:29.820
57786
2019-09-03T12:38:11.940
35697
question
reactjs
react functional component 再レンダーされない


stateの配列の変更は、immutablity-helperなるものを使うそうです。


CC BY-SA 4.0
2019-09-03T05:01:03.707
57791
2019-09-03T05:01:03.707
35697
57786
answer


useState hookでは、同じ値で更新した場合、子のレンダリングを回避します。ここで "同じ値" の意味は`Object.is(古いstate,
新しいstate)`が`true`という意味です。

出典: <https://ja.reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-
update>

> state 更新の回避
>
> 現在値と同じ値で更新を行った場合、React は子のレンダーや副作用の実行を回避して処理を終了します。（React は Object.is
> による比較アルゴリズム を使用します）

添付されたコードを見ると`todo.splice`で`todo`を直接変更し、その同じ`todo`Arrayインスタンスを`setTodo`に渡しているため、`Object.is`による比較は`true`となることが推察されます。

以下、解決策のひとつです。

[code]

    const handleRemove = (i: number) => {
        // todo をコピーして、新しいArrayインスタンスを作成する。
        const newTodo = [...todo];
        // ここで Object.is(todo, newTodo) === false となります。
        // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/is
    
        // newTodo 配列のi番目から1つ目のデータを除外
        newTodo.splice(i, 1);
    
        setTodo(newTodo);
    }
    
[/code]

もしくは次のようにも書けます。

[code]

    const handleRemove = (i: number) => {
        setTodo([...todo.slice(0, i), ...todo.slice(i + 1)]);
    }
    
[/code]

なお`handleAdd`内で使われている`concat`は新しいArrayインスタンスを返すので子のレンダリングは必ず行われます。`push`を使って直接変更し、同じ`todo`を渡した場合は再レンダリングされません。

[code]

    // 再描画されない例
    const handleAdd = (e) => {
        e.preventDefault();
        todo.push(e.target.title.value);
        setTodo(todo);
        console.log(todo); // 新しい要素が追加された`todo`が出力される
        e.target.title.value = "";
    };
    
[/code]


CC BY-SA 4.0
2019-09-03T09:46:05.213
57806
2019-09-03T12:38:11.940
2019-09-03T12:38:11.940
32959
32959
57786
answer