3
votes

From react-redux official docs:

enter image description here

When an action is dispatched, useSelector() will do a reference comparison of the previous selector result value and the current result value. If they are different, the component will be forced to re-render. If they are the same, the component will not re-render.

If I did something like this:

Redux state

state = {
  groupAB: {
    propA: valueA,
    propB: valueB
  },
  propC,
  // ETC
}

SomeComponent.tsx

const propA = useSelector((state) => state.groupAB.propA);

How would this component behave if: the groupAB object changes its reference (because propB has mutated) but propA: valueA remains the same?

What I expect:

The component should not re-render even if groupAB changes, because what the useSelector() cares about is the return, which is valueA, in this example. Am I right?

2

2 Answers

1
votes

In the doc we have:

When the function component renders, the provided selector function will be called and its result will be returned from the useSelector() hook. (A cached result may be returned by the hook without re-running the selector if it's the same function reference as on a previous render of the component.)

You're using arrow function as a selector function, so the reference never be the same in each render, and it'll never read the result from cache.

By the way, we also have:

when an action is dispatched to the Redux store, useSelector() only forces a re-render if the selector result appears to be different than the last result.

Because the returned value from the selector function is a primitive -it's a simple string and not recognized as part of object or something related to the object (that I think is your concern)- and as you said it's not changing in this scenario, it will consider the same to the previous one, and will not force the component to be re-rendered.

So, at the end, the function will be fire each time that the component re-rendered, but changing part of store which not lead to change the mentioned primitive value, will not end up with re-rendering component.

0
votes

CodeSandbox link

Just tested and it works like expected.

Component will not re-render if the return value does not change. Even if it's a deep nested value, like the example state.groupAB.propA = "valueA".

Of course, to test this behavior, you got to wrap it in React.memo(), otherwise it will re-render anyway whenever its parent re-renders.

enter image description here


App.js

import React from "react";
import "./styles.css";
import { useDispatch, useSelector } from "react-redux";
import SomeComponent from "./SomeComponent";

export default function App() {
  console.log("App rendering...");

  const state = useSelector((state) => state);
  const dispatch = useDispatch();

  function updateState() {
    dispatch({ type: "UPLOAD_GROUP_AB_AND_PROP_B" });
  }

  return (
    <div>
      <div>State: {JSON.stringify(state)}</div>
      <button onClick={updateState}>Update state</button>
      <SomeComponent />
    </div>
  );
}

reducer.js

const initialState = {
  groupAB: {
    propA: "valueA",
    propB: "valueB"
  },
  propC: "valueC"
};

export const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "UPLOAD_GROUP_AB_AND_PROP_B": {
      return {
        // WILL CREATE A NEW STATE OBJ
        ...state,
        groupAB: {
          // WILL CREATE A NEW groupAB OBJ
          ...state.groupAB, // propA WILL REMAIN THE SAME
          propB: "newValueB" // WILL UPDATE propB VALUE
        }
      };
    }
    default: {
      return state;
    }
  }
};

SomeComponent.js

import React from "react";
const { useSelector } = require("react-redux");

function SomeComponent() {
  console.log("SomeComponent rendering...");

  const propA = useSelector((state) => state.groupAB.propA);

  return (
    <div>
      <hr />
      <div>This is SomeComponent</div>
      state.groupAB.propA: {propA}
    </div>
  );
}

export default React.memo(SomeComponent);

index.js

import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";

import App from "./App";
import { reducer } from "./reducer";

const store = createStore(reducer);

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);