React+Redux Custom Hook (only update at certain conditions)

Viewed 897

I've got a component along the lines of the following:

function Comp() {
   const counter = useSelector(state => state.counter);
   console.log('I will only re-render on change of state.counter, not state.info etc');
   return <div>{counter}</div>
}

This only re-renders when state.counter has been changed, i.e. not when another part of the redux state is updated. Which is good.

My render function is actually very big (and performance intensive - actually this component is used in many instances), and therefore not only rendering the counter state.

I figure I only want to re-render upon certain conditions, such as the counter becoming 5 or was recently 5 but just changed to something else. But not rerender upon the counter was 3 and now 4.

For this purpose, I was looking at creating a custom hook, such as:

function useIsCounterFive() {
  const counter = useSelector(state => state.counter);
  const [counterIsFive, setCounterIsFive] = useState(false);
  useEffect(() => {
     if(counter === 5) {
         setCounterIsFive(true);
     }
     else {
         setCounterIsFalse(false);
     }
     // Or as an alternative to the above else block:
     /*
     else if(counterIsFive === true) {
         setCounterIsFalse(false);
     }
     */

  }, [counter])

  return counterIsFive;
  
}

and then having the Comp component looking something like:

function Comp() {
  const isFive = useIsCounterFive();
  console.log('I only want to be run when isFive changes, the same way as I only was running in the previous example when the _counter_ variable changed');
  return <div>bla bla</div>
}

Even though a change of the counter from 3 to 4 should keep the state being false, the hook is still causing the Comp component to re-render. I was hoping to obtain a result such when using the useSelector which only caused my Comp to run/re-render upon a change of the selected value.

Any insights? :)

2 Answers

You could pass selector props to your function and use React.memo to memorize the function so that it prevents rendering when you don't need to.

function ThatWrapsCounter() {
 const counter = useSelector(state => state.count);

 return <MemoizedCounter count={count} />
}


function Counter({ count }) {
}

function checkForFive(prevProps, props) {
  if (props.count === 5) return true;
  return false;
}

const MemoizedCounter = React.memo(Counter, checkForFive);

Here is an example that re renders when counter is not 5 and when it is 5 using a selector:

const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;

const initialState = { count: 0 };
//action types
const ADD = 'ADD';
//action creators
const add = () => ({
  type: ADD,
});
const reducer = (state, { type, payload }) => {
  if (type === ADD) {
    return { ...state, count: state.count + 1 };
  }
  return state;
};
//selectors
const selectCount = (state) => state.count;
const createSelectCountRule = (rule) =>
  createSelector([selectCount], rule);
//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 Counter = () => {
  const selectCount = React.useMemo(
    () => createSelectCountRule((count) => count === 5),
    []
  );
  const count = useSelector(selectCount);
  console.log('Rendering counter with:', count);
  return count ? 'count is 5' : 'count is not 5';
};
const App = () => {
  const dispatch = useDispatch();
  return (
    <div>
      <button onClick={() => dispatch(add())}>add</button>
      <Counter />
    </div>
  );
};

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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>

Related