Skip to content

Commit

Permalink
Add: Add a useShallowEqualSelector hook
Browse files Browse the repository at this point in the history
The useShallowEqualSelector hooks allows to avoid re-renders if an
object is selected from the redux store but its value(s) didn't change.
With the standard selector which uses `===` comparison even updating an
object's value to the same value will cause a re-render (because a new
state object is created).

This pattern can be found at https://react-redux.js.org/api/hooks#recipe-useshallowequalselector
  • Loading branch information
bjoernricks committed Jun 6, 2024
1 parent ce1c182 commit a6d9b92
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 0 deletions.
96 changes: 96 additions & 0 deletions src/web/hooks/__tests__/useShallowEqualSelector.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/* SPDX-FileCopyrightText: 2024 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

/* eslint-disable react/prop-types */

import {useCallback} from 'react';
import {useSelector, useDispatch} from 'react-redux';
import {configureStore} from '@reduxjs/toolkit';

import {describe, test, expect, testing} from '@gsa/testing';

import {fireEvent, rendererWith, screen} from 'web/utils/testing';

import useShallowEqualSelector from '../useShallowEqualSelector';

const reducer = (state = {value: 0}, action) => {
switch (action.type) {
case 'increment':
return {...state, value: 1};
default:
return state;
}
};

const update = () => ({type: 'increment'});

const TestComponent1 = ({renderCallback}) => {
const state = useSelector(state => state.counter);
const dispatch = useDispatch();
const updateCounter = useCallback(() => dispatch(update()), [dispatch]);
renderCallback();
return (
<div>
<div data-testid="counter">{state.value}</div>
<button data-testid="update" onClick={updateCounter}>
Increment
</button>
</div>
);
};

const TestComponent2 = ({renderCallback}) => {
const state = useShallowEqualSelector(state => state.counter);
renderCallback();
return (
<div>
<div data-testid="shallowCounter">{state.value}</div>
</div>
);
};

describe('useShallowEqualSelector tests', () => {
test('should return the selected state', () => {
const renderCount = testing.fn();
const shallowRenderCount = testing.fn();
const store = configureStore({
reducer: {
counter: reducer,
},
middleware: () => [],
});

const {render} = rendererWith({store});

render(
<>
<TestComponent1 renderCallback={renderCount} />
<TestComponent2 renderCallback={shallowRenderCount} />
</>,
);

const counter = screen.getByTestId('counter');
const shallowCounter = screen.getByTestId('shallowCounter');
expect(counter).toHaveTextContent('0');
expect(shallowCounter).toHaveTextContent('0');
expect(renderCount).toHaveBeenCalledTimes(1);
expect(shallowRenderCount).toHaveBeenCalledTimes(1);

const updateCounter = screen.getByTestId('update');
fireEvent.click(updateCounter);

expect(counter).toHaveTextContent('1');
expect(renderCount).toHaveBeenCalledTimes(2);
expect(shallowCounter).toHaveTextContent('1');
expect(shallowRenderCount).toHaveBeenCalledTimes(2);

fireEvent.click(updateCounter);

expect(counter).toHaveTextContent('1');
expect(renderCount).toHaveBeenCalledTimes(3);
expect(shallowCounter).toHaveTextContent('1');
expect(shallowRenderCount).toHaveBeenCalledTimes(2);
});
});
20 changes: 20 additions & 0 deletions src/web/hooks/useShallowEqualSelector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* SPDX-FileCopyrightText: 2024 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import {useSelector, shallowEqual} from 'react-redux';

/**
* A hook to use a redux selector with shallow equality check
*
* By default useSelector uses a strict equality check `===` to determine if the
* state has changed. This hook uses a shallow equality check to determine if the
* state has changed.
*
* @param {*} selector A redux selector
* @returns {*} The selected state
*/
const useShallowEqualSelector = selector => useSelector(selector, shallowEqual);

export default useShallowEqualSelector;

0 comments on commit a6d9b92

Please sign in to comment.