Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

equalityFn in Redux useSelector doesn't work with objects and arrays

I think equalityFn in useSelector doesn't work as it should. It works great with simple strings/numbers but it doesn't work with objects and arrays. It always gets 'item' and 'prevItem' equal in this case. Check example here:

import React, { Component } from 'react';
import { render } from 'react-dom';
import { createStore, combineReducers } from 'redux';
import { connect, Provider, useSelector, useDispatch } from 'react-redux';
import './style.css';

const countReducer = (state = { count: [] }, action) => {
  switch (action.type) {
    case 'INC': 
      state.count.push('1')
      return state;
    default: return state;
  }
}

const reducers = combineReducers({
  counter: countReducer,
})

const store = createStore(reducers);

function App () {
  const dispatch = useDispatch()
  const count = useSelector(state => state.counter.count, (item, previousItem) => {
    console.log('old')
    console.log(previousItem)
    console.log('new')
    console.log(item)
    if (item.length === previousItem.length) {
      console.log('Equal')
      return true
    } else {
      console.log('Not Equal')
      return false
    }
  })
  return (
    <div>
      <button onClick={() => dispatch({
        type: 'INC'
      })}>Increment</button>
      <div>Value: {count[0]}</div>
    </div>
  )
}

const AppContainer = connect()(App);

render(
  <Provider store={store}>
    <AppContainer />
  </Provider>,
  document.getElementById('root')
);
like image 336
Nick Avatar asked Sep 03 '25 15:09

Nick


1 Answers

state.count.push('1')

Do not mutate the state.

One of redux's core concepts is that the state is immutable. Since the state is assumed to be immutable, a super-quick reference equality check (ie, ===) is all that's needed to tell whether the state has changed. If you violate that assumption by mutating the state, then the state before and the state after are the same object, and so it looks like nothing has changed when in fact it has.

Instead, create a new array. With the spread syntax, you can do so like this:

case 'INC': 
  return {
    ...state,
    count: [...state.count, '1']
  }

See also their recommendations on this page: Immutable Update Patterns

like image 160
Nicholas Tower Avatar answered Sep 05 '25 03:09

Nicholas Tower