汎用的なsetState用の関数

Reactを使う上では欠かせないのが、setState()です。今回は、この関数をより使い回しやすくする小さなTipsを紹介します。

以降、ボタンを押すと数値がカウントされるコンポーネントを例としてみていきます。


import React, { Component } from 'react';

class App extends Component {
    constructor() {
        super();
        this.state = {
            counter: 0,
        };
    }

    onIncrement = (event) => {
        this.setState((prevState) => ({
            counter: prevState.counter + 1
        }));
    }

    render() {
        const { counter } = this.state;
        return (
            <div>
                <h1>Counter</h1>
                <p>{counter}</p>

                <button type="button" onClick={this.onIncrement}>
                    Increment
                </button>
            </div>
        );
    }
}

export default App;

setState()の引数にはオブジェクトを渡すことが多いですが、関数を渡すこともできます。関数の場合は、変更前の状態(state)オブジェクトを引数に取ることができます。関数の第1引数(ここではprevState)に状態オブジェクトが格納されます。

尚、オブジェクトの場合でもthis.state.counterのようにすれば、状態オブジェクトにアクセスは可能です。

改善案

Reactは状態オブジェクトの変更が常時行われ、特にsetState()は多様されます。先のサンプルのように簡易なロジックでメソッドを1つずつ定義するとコードが膨れあがってしまいます。ある程度の規模になってくると、最適化できないものかと思い始めます。

よく用いられる手法としては、セット用の関数を定義することのようです。以下を先のクラスの外に定義します。


const byPropKey = (propertyName, value) => () => ({
    [propertyName]: value
});

上記を使用するため、デクリメントのボタンを作成してみます。

<button
    type="button"
    onClick={event => this.setState(byPropKey('counter', counter - 1 ))}
>
	Decrement
</button>

インクリメントボタンも書き直しができます。もうonIncrement()は不要です!


<button
    type="button"
    onClick={event => this.setState(byPropKey('counter', counter + 1 ))}
>
	Increment
</button>

これの素晴らしいところは、インクリメント・デクリメントに限らず、すべてのsetState()の状態更新が汎用的に行える点です。今回利用していませんが、内部ではeventオブジェクトを参照できますし、入力値などもそのまま利用できます。

また、setState()の引数にオブジェクトではなく関数を渡しているので、最初に解説したように関数内では変更前の状態オブジェクトを引数に取ることも可能です。


const byPropKey = (propertyName, value) => (prevState) => ({
	// 何かしらのセット処理
});

ハードコーティングより、コード量をだいぶ省略することができて便利です。