DRYな備忘録

Don't Repeat Yourself.

react-reduxでmapStateToPropsが呼ばれてるのに再度renderされない問題

こういうコード書いて、末端コンポーネントで直接stateの変化をsubscribeしようとした

class Logs extends React.Component {
    render() {
        return <ul>
            {this.props.logs.map((log) => {
                return <li>{JSON.stringify(log)}</li>
            })}
        </ul>
    }
}

Logs = connect((state) => {
    return { logs: state.logs }
})(Log)

export default Logs

Actionが発行されて、Dispatcherに通って、Dispatcherでは新しい値が来てるように見えてるんだけど、このpropsの更新に起因する再度のrenderには入っていない。

調査

mapStateToProps は、Providerだか大親コンポーネントstateをconnectしたこのコンポーネントpropsにmapするわけだけれど、とりあえずここにdebuggerを仕込む

Logs = connect((state) => {
+   debugger
    return { logs: state.logs }
})(Logs)

export default Logs

そうすっと、 ConnectupdateStatePropsIfNeededといういかにもそれらしいところでそれっぽい挙動がひっかかった

f:id:otiai10:20160419200630p:plain

ソースコード
現在自分が持っているthis.statePropsと、新しく降ってきたnextStatePropsを比較して、差分が無ければ何もせずfalse、差分があれば置き換えてtrueを返す。

問題は、この、this.statePropsnextStatePropsが等しいので、falseを返してる。なぜ。

this.stateProps、お前いつの間にnextStateProps先取りしてるんだ...

といった気持ちです。

原因:Reducerでのstateの扱いの注意

let store = createStore((state = {position:{top: 0, left: 0}, logs: []}, action) => {
  let logs = state.logs // <- ここと
  logs.unshift(action) // <- ここ
  switch (action.type) {

dispatcherに来るstateはcurrentStateで、ここでArrayやObjectのようにデフォルトで参照を渡す*1ものを直接どっかに置いてそれをいじっても参照元currentState(ただしくはthis.statePropsになるわけだけど)も一緒に変わっちゃってるので、この後のupdateStatePropsIfNeededで、no needな状態になっちゃうというわけ。なので、以下のように変更。

let store = createStore((state = {position:{top: 0, left: 0}, logs: []}, action) => {
-  let logs = state.logs
+  let logs = state.logs.slice(0)
  logs.unshift(action)
  switch (action.type) {

雑感

  • react-redux、さいしょ敷居高いなーと思ってたけど、クセさえ覚えれば良いものっぽい

DRY

*1:じゃっかんのごへいはありますが