0

We know that if we want to re-render a react component connected by react-redux, we should return a new array in reducer when we dispatch a related action. My problem is that why react component can re-render when redux reducer do not return a new array? When I click App component button, first app name becomes 'aaa', why???

actions:

export function setAppName(name: string) {
  return {
    type: 'SET_APP_NAME',
    name,
  }
}

reducers:

export function appInfos(state = {
  appInfos: [
    {name: 'ccc'},
    {name: 'ddd'},
  ]
}, action: any) {
  switch(action.type) {
    case 'SET_APP_NAME':
      // just return origin array, not a new array
      const appInfos = state.appInfos;
      appInfos[0].name = action.name
      return Object.assign({}, state, { appInfos })
    default:
      return state;
  }
}

component App:

function mapStateToProps(state: any) {
  return {
    app: state.appInfos
  }
}

function App() {
  const { app } = useSelector(mapStateToProps)
  const dispatch = useDispatch()
  const onClick = useCallback(() => {
    dispatch(setAppName('aaa'))
  }, [dispatch])

  return (
    <div className="app">
      {app.appInfos.map((info: any) => {
        return <div key={info.name}>{info.name}</div>
      })}
      <button onClick={onClick}>click</button>
    </div>
  );
}

container & store:

const store = createStore(combineReducers({
  appInfos
}))

ReactDOM.render(
  <Provider store={store}>
    <React.StrictMode>
    <App />
  </React.StrictMode>
  </Provider>,
  document.getElementById('root')
);
  • `appInfos[0].name = action.name` this is mutating the current state object. See: [How to update single value inside specific array item in redux](https://stackoverflow.com/q/35628774/1218980) – Emile Bergeron Jul 26 '21 at 17:35

1 Answers1

1

You're mixing up usage for connect and useSelector, and that's causing unnecessary renders.

connect accepts a mapStateToProps function as an argument, which must return an object because all fields will become new props to the component. That component will re-render if any individual field changes to a new reference:

https://react-redux.js.org/using-react-redux/connect-mapstate

However, useSelector accepts a selector function that must return one value, and it will re-render if that one reference changes to a new value:

https://react-redux.js.org/api/hooks#equality-comparisons-and-updates

Right now, you are writing a function that is always returning a new object reference. Your function will always run after every dispatched action, and thus it will always force the component to re-render.

Change it to just be:

function selectAppInfo(state) {
  return state.appInfos
}
markerikson
  • 63,178
  • 10
  • 141
  • 157