useSelector updating even if value hasn't changed

Viewed 109

I've got a component that subscribes to a portion of the store and triggers an effect after

const Component = (name) => {

  const query = useSelector((state) => getQuerySelector(state, name), shallowEqual);

  
  useEffect(() => {
    doStuff();
  }, [query]);

}

The selector looks like

const getQuerySelector = (state, name) => state.innerReducer[name].query;

Even if when the reducer updates the query with the same value like {value: true} (it's a result of a computation of other values) the selector seems to be updated every time. This is part of my reducer:

case 'ACTION_X': 
  const query = {aProp: aValue, someOtherProp: someValue}
   
  return {
    ...otherData,
    query
  }

I tried to memoize the selector with createSelector as well, but with no luck.

1 Answers

I was not able to reproduce the behavior you describe using all code you provided. Maybe you can use my code snippet to reproduce the behavior you describe:

const {
  Provider,
  useDispatch,
  useSelector,
  shallowEqual,
} = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const name = 'name';
const initialState = {
  innerReducer: {
    name: {
      query: { aProp: 8, someOtherProp: 8 },
    },
  },
};
//action types
const ACTION_X = 'ACTION_X';
//action creators
const actionX = () => ({
  type: ACTION_X,
});
const reducer = (state, { type }) => {
  console.log('reducing action:', type);
  if (type === ACTION_X) {
    const query = { aProp: 8, someOtherProp: 8 };
    return {
      ...state,
      innerReducer: {
        ...state.innerReducer,
        [name]: {
          ...state.innerReducer[name],
          query,
        },
      },
    };
  }

  return state;
};
//selectors
const getQuerySelector = (state, name) =>
  state.innerReducer[name].query;
//creating store with redux dev tools
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducer,
  initialState,
  composeEnhancers(
    applyMiddleware(() => (next) => (action) =>
      next(action)
    )
  )
);
const App = () => {
  const query = useSelector(
    (state) => getQuerySelector(state, name),
    //this shallowEqual will make sure that
    //{ aProp: 8, someOtherProp: 8 } quals { aProp: 8, someOtherProp: 8 }
    shallowEqual
  );
  const dispatch = useDispatch();
  console.log('rendering app');
  React.useEffect(
    () => console.log('Query in effect:', query),
    [query]
  );
  return (
    <div>
      <button onClick={() => dispatch(actionX())}>
        dispatch action x
      </button>
      <pre>{JSON.stringify(query, undefined, 2)}</pre>
    </div>
  );
};
console.log(
  'using shallowEqual:',
  shallowEqual(
    { aProp: 8, someOtherProp: 8 },
    { aProp: 8, someOtherProp: 8 }
  )
);
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<div id="root"></div>

Maybe you can have a look at the redux devtools and make sure that query state didn't change. Maybe the component re renders because the parent is re rendered and your component is not pure, maybe the component is re rendered because parent is passing in different props. Could be tons of reasons why the component re renders but code in your question does not show any.

Related