mr. gugr
mr. gugr

Reputation: 79

How to optimize React/Redux chat perfomance?

I have a chat based on React and Redux.

Chat container receives messageIds and using messageIds.map() I render component for each message. This component is connected to Redux, so it receives messageId and updates only if messageId was changed.

But in profiler I see a lot of (React Tree Reconciliation: Completed Root) events on new message received, which take about 6ms. How to prevent this reconcilitation or how to optimize it?

For example I have 30 messages in chat, and on each new message reconciliation takes about 6ms * 30 messages = 180 ms, even if message component was not rerendered. Below I provided screenshots of profiler.

Message received action

Zoomed reconciliation event

Upvotes: 1

Views: 410

Answers (1)

HMR
HMR

Reputation: 39270

I created an example with 3 common mistakes using react-redux connect and needless re renders:

  1. mapStateToProps always returns a new object (state=>({val:{new:reference}}) you can prevent this by memoizing the result: ()=>{const memoizedResult=createSelector(...);return state=>memoizedResult(state)

  2. Passing a new object as prop every time (called bad prop)

  3. Passing a new function as callback every time (called bad callback prop)

const { Provider, connect } = ReactRedux;
const { createStore } = Redux;

const store = createStore(() =>
  //reducer always returns a new object
  ({
    val: 1,
  })
);
function App({ a }) {
  return (
    <div>
      <button onClick={a}>re render</button>
      <BadContainer message="bad container" />
      <GoodContainer
        message="bad prop"
        badProp={{ a: 22 }}
      />
      <GoodContainer
        message="bad callback prop"
        badCallback={() => 88}
      />
      <GoodContainer message="good container" />
    </div>
  );
}
function Message(props) {
  const rendered = React.useRef(0);
  rendered.current++;
  return (
    <div>
      {props.message} rendered: {rendered.current} times
    </div>
  );
}
const AppContainer = connect(() => ({ a: {} }), {
  a: () => ({ type: 'a' }),
})(App);
const BadContainer = connect(state =>
  //bad map state, always returns new object for props
  ({
    object: { newObject: state.val },
  })
)(Message);
const GoodContainer = connect(() => {
  //prepare memoized function that will only re create props
  //  if parameters to it changed (usually done with reselect)
  const memProps = ((lastVal, lastResult) => val => {
    if (lastVal !== val) {
      lastResult = {
        object: { newObject: val },
      };
      lastVal = val;
    }
    return lastResult;
  })();
  //return optimized map state to props using memoization
  return state => memProps(state.val);
}, {})(Message);
ReactDOM.render(
  <Provider store={store}>
    <AppContainer />
  </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.4/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.1.1/react-redux.min.js"></script>
<div id="root"></div>

Upvotes: 1

Related Questions