Search code examples
javascriptreactjsreduxreact-hooksredux-thunk

How to prevent useSelector from causing unnecessary renders?


I'm using useSelector hook to retrieve the values of my reducer, but it is causing a lot of unnecessary renders on my application.

It doesn't matter which property I'm using on a component, since they are all getting the same state object from the reducer, every time one property changes, all components that use useSelector are rendered.

This is the reducer:

const initialState = {
   productsDataState: [], // => this is populated by multiple objects
   searchProducts: [],
   isSearchOn: false,
   inputValue: '',
   listOrder: [],
   toastify: ['green', ''],
   toastifyOpen: false
}

const reducer = ((state = initialState, action) => {
   switch (action.type) {
      case actionTypes.UPDATE_PRODUCT:
         return {
            ...state,
            productsDataState: action.products,
            listOrder: action.listOrder
         }
      case actionTypes.SET_TOASTIFY:
         return {
            ...state,
            toastify: action.toastify,
            toastifyOpen: action.open
         }
      case actionTypes.SET_SEARCH:
         return {
            ...state,
            searchProducts: action.searchProducts,
            isSearchOn: action.isSearchOn,
            inputValue: action.inputValue
         }
      default:
         return state
   }
})

One of the components is using isSearchOn, which is a boolean, so I solved the problem checking if the value is true before rendering it:

 const { isSearchOn } = useSelector(state => state.isSearchOn && state)

But that's not the case for all components. The one I'm stuck right now uses the productsDataState property, which is an array of objects. I can't just make a simple validation before returning state. I thought about storing the initial value in a useState, make a deep comparison between the current value and the past one before returning the state, which would work similarly to what I did in the other component, but I can't see how this would be a good approach.

const { productsDataState } = useSelector(state => state)

Is there a way where I could take advantage of useSelector without comprimising the performance of the application?

I've being reading a lot and making a lot of tests, but I couldn't find a good way to do that so far.

I'd like to keep useSelector, but I'm open to suggestions, even if it involves other libraries.


Solution

  • What you should be doing is not selecting whole state, just the part you need :)

    const productsDataState = useSelector(state => state.productsDataState)
    

    @edit

    If you want to select multiple data with one selector you will cause it to change reference if you would try to use an object for example.

    const { productsDataState, other } = useSelector(state => ({ productsDataState: state.productsDataState, other: state.other }))
    

    this will cause rerender on any state change as redux use strict equality check by default.

    You should listen to official documentation and select each state separately

    Call useSelector() multiple times, with each call returning a single field value