Skip to content

Commit 8b1c959

Browse files
authored
RI-8208: Adopt typed Redux hooks (useAppDispatch / useAppSelector) (#5993)
* adopt typed Redux hooks Replace direct `useDispatch`/`useSelector` from `react-redux` with the typed `useAppDispatch`/`useAppSelector` wrappers from `uiSrc/slices/hooks` across all production source files (including Storybook stories). Functionally identical at runtime — the typed hooks resolve through the same react-redux Provider context — but the typed signatures carry `AppDispatch` and `RootState` so callers no longer need to cast or rely on the `redux-thunk` v2 implicit thunk acceptance. This is a prerequisite for the upcoming Redux / RTK upgrade so that the upgrade itself becomes a small, reviewable change rather than a 350-file refactor mixed with a dependency bump. * update Redux hook mocks to target typed hooks module Migrate spec files from mocking `react-redux` (`useDispatch`/`useSelector`) to mocking `uiSrc/slices/hooks` (`useAppDispatch`/`useAppSelector`), which is what production code now imports after the typed-hooks adoption. Each factory keeps `...jest.requireActual('uiSrc/slices/hooks')` so any hook not explicitly stubbed falls through to the real implementation, matching the previous `...jest.requireActual('react-redux')` semantics and avoiding `is not a function` errors when only one hook is overridden. Mostly mechanical (codemod-driven); a couple of files using namespace imports of `react-redux` were updated by hand to keep the same test behavior. The broader mocking pattern (jest.mock of the hooks layer itself) is preserved intentionally — moving to a real `<Provider>` + real store style is out of scope here and will be tracked separately. * fix lint * add eslint rule
1 parent 07a0969 commit 8b1c959

384 files changed

Lines changed: 1551 additions & 1455 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.eslintrc.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,24 @@ module.exports = {
194194
'prefer-template': 'error',
195195
'prefer-const': 'error',
196196
'@typescript-eslint/no-unused-vars': noUnusedVarsConfig,
197+
// Force every UI callsite onto the typed Redux hooks so the
198+
// `AppDispatch` (incl. thunk overload) and `RootState` types are
199+
// never accidentally dropped. The wrapper file itself
200+
// (`uiSrc/slices/hooks.ts`) is allowed to import the originals — see
201+
// the override below.
202+
'no-restricted-imports': [
203+
'error',
204+
{
205+
paths: [
206+
{
207+
name: 'react-redux',
208+
importNames: ['useDispatch', 'useSelector'],
209+
message:
210+
'Use useAppDispatch / useAppSelector from uiSrc/slices/hooks instead.',
211+
},
212+
],
213+
},
214+
],
197215
'import/order': [
198216
1,
199217
{
@@ -223,6 +241,14 @@ module.exports = {
223241
],
224242
},
225243
},
244+
// Typed Redux hooks wrapper — only file allowed to import the bare
245+
// `useDispatch` / `useSelector` from `react-redux` (it's what wraps them).
246+
{
247+
files: ['redisinsight/ui/src/slices/hooks.ts'],
248+
rules: {
249+
'no-restricted-imports': 'off',
250+
},
251+
},
226252
// UI test files
227253
{
228254
files: ['redisinsight/ui/**/*.spec.ts', 'redisinsight/ui/**/*.spec.tsx'],

redisinsight/ui/src/App.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { ReactElement, useEffect } from 'react'
2-
import { Provider, useSelector } from 'react-redux'
2+
import { Provider } from 'react-redux'
3+
import { useAppSelector } from 'uiSrc/slices/hooks'
34

45
import { Route, Switch } from 'react-router-dom'
56
import { store } from 'uiSrc/slices/store'
@@ -46,7 +47,7 @@ const AppWrapper = ({ children }: { children?: ReactElement[] }) => (
4647
</Provider>
4748
)
4849
const App = ({ children }: { children?: ReactElement[] }) => {
49-
const { loading: serverLoading } = useSelector(appInfoSelector)
50+
const { loading: serverLoading } = useAppSelector(appInfoSelector)
5051
useEffect(() => {
5152
if (!serverLoading) {
5253
removePagePlaceholder()

redisinsight/ui/src/components/analytics-tabs/AnalyticsTabs.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useEffect, useMemo } from 'react'
2-
import { useDispatch, useSelector } from 'react-redux'
2+
import { useAppDispatch, useAppSelector } from 'uiSrc/slices/hooks'
33
import { useParams, useHistory } from 'react-router-dom'
44

55
import { Pages } from 'uiSrc/constants'
@@ -22,14 +22,14 @@ import Tabs, { TabInfo } from 'uiSrc/components/base/layout/tabs'
2222
import { Text } from 'uiSrc/components/base/text'
2323

2424
const AnalyticsTabs = () => {
25-
const { viewTab } = useSelector(analyticsSettingsSelector)
25+
const { viewTab } = useAppSelector(analyticsSettingsSelector)
2626
const connectionType = useConnectionType()
27-
const { currentStep } = useSelector(appFeatureOnboardingSelector)
27+
const { currentStep } = useAppSelector(appFeatureOnboardingSelector)
2828
const history = useHistory()
2929

3030
const { instanceId } = useParams<{ instanceId: string }>()
3131

32-
const dispatch = useDispatch()
32+
const dispatch = useAppDispatch()
3333

3434
useEffect(() => {
3535
if (

redisinsight/ui/src/components/bottom-group-components/BottomGroupComponents.stories.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Meta, StoryObj } from '@storybook/react-vite'
22
import React, { useEffect } from 'react'
3-
import { useDispatch } from 'react-redux'
3+
import { useAppDispatch } from 'uiSrc/slices/hooks'
44

55
import BottomGroupComponents from './BottomGroupComponents'
66
import {
@@ -23,7 +23,7 @@ type Story = StoryObj<typeof meta>
2323
export const CLI: Story = {
2424
decorators: [
2525
(Story) => {
26-
const dispatch = useDispatch()
26+
const dispatch = useAppDispatch()
2727

2828
useEffect(() => {
2929
dispatch(resetCliHelperSettings())
@@ -41,7 +41,7 @@ export const CLI: Story = {
4141
export const CLIHelper: Story = {
4242
decorators: [
4343
(Story) => {
44-
const dispatch = useDispatch()
44+
const dispatch = useAppDispatch()
4545

4646
useEffect(() => {
4747
dispatch(resetCliHelperSettings())
@@ -60,7 +60,7 @@ export const CLIHelper: Story = {
6060
export const Monitor: Story = {
6161
decorators: [
6262
(Story) => {
63-
const dispatch = useDispatch()
63+
const dispatch = useAppDispatch()
6464

6565
useEffect(() => {
6666
dispatch(resetCliHelperSettings())

redisinsight/ui/src/components/bottom-group-components/BottomGroupComponents.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react'
2-
import { useSelector } from 'react-redux'
2+
import { useAppSelector } from 'uiSrc/slices/hooks'
33
import cx from 'classnames'
44
import styled from 'styled-components'
55

@@ -26,8 +26,8 @@ const GroupComponents = styled.div`
2626
`
2727

2828
const BottomGroupComponents = () => {
29-
const { isShowCli, isShowHelper } = useSelector(cliSettingsSelector)
30-
const { isShowMonitor } = useSelector(monitorSelector)
29+
const { isShowCli, isShowHelper } = useAppSelector(cliSettingsSelector)
30+
const { isShowMonitor } = useAppSelector(monitorSelector)
3131

3232
return (
3333
<GroupComponentsWrapper>

redisinsight/ui/src/components/bottom-group-components/components/bottom-group-minimized/BottomGroupMinimized.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useEffect } from 'react'
2-
import { useDispatch, useSelector } from 'react-redux'
2+
import { useAppDispatch, useAppSelector } from 'uiSrc/slices/hooks'
33
import { useParams } from 'react-router-dom'
44

55
import { EXTERNAL_LINKS } from 'uiSrc/constants/links'
@@ -38,9 +38,9 @@ import {
3838
const BottomGroupMinimized = () => {
3939
const { instanceId = '' } = useParams<{ instanceId: string }>()
4040
const { isShowCli, cliClientUuid, isShowHelper, isMinimizedHelper } =
41-
useSelector(cliSettingsSelector)
42-
const { isShowMonitor, isMinimizedMonitor } = useSelector(monitorSelector)
43-
const dispatch = useDispatch()
41+
useAppSelector(cliSettingsSelector)
42+
const { isShowMonitor, isMinimizedMonitor } = useAppSelector(monitorSelector)
43+
const dispatch = useAppDispatch()
4444

4545
useEffect(
4646
() => () => {

redisinsight/ui/src/components/bulk-actions-config/BulkActionsConfig.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useEffect, useRef } from 'react'
2-
import { useDispatch, useSelector } from 'react-redux'
2+
import { useAppDispatch, useAppSelector } from 'uiSrc/slices/hooks'
33
import { Socket } from 'socket.io-client'
44

55
import {
@@ -29,23 +29,23 @@ import { useIoConnection } from 'uiSrc/services/hooks/useIoConnection'
2929
import { getBaseUrl } from 'uiSrc/services/apiService'
3030

3131
const BulkActionsConfig = () => {
32-
const { id: instanceId = '', db } = useSelector(connectedInstanceSelector)
33-
const { isConnected } = useSelector(bulkActionsSelector)
32+
const { id: instanceId = '', db } = useAppSelector(connectedInstanceSelector)
33+
const { isConnected } = useAppSelector(bulkActionsSelector)
3434
const {
3535
isActionTriggered: isDeleteTriggered,
3636
generateReport,
3737
filter,
3838
search,
3939
confirmedThrough,
40-
} = useSelector(bulkActionsDeleteSelector)
41-
const { token } = useSelector(appCsrfSelector)
40+
} = useAppSelector(bulkActionsDeleteSelector)
41+
const { token } = useAppSelector(appCsrfSelector)
4242
const socketRef = useRef<Nullable<Socket>>(null)
4343
const connectIo = useIoConnection(getSocketApiUrl('bulk-actions'), {
4444
token,
4545
query: { instanceId },
4646
})
4747

48-
const dispatch = useDispatch()
48+
const dispatch = useAppDispatch()
4949

5050
useEffect(() => {
5151
if (!isDeleteTriggered || !instanceId || socketRef.current?.connected) {

redisinsight/ui/src/components/cli/components/cli-body/CliBody/CliBody.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { Ref, useEffect, useRef, useState } from 'react'
22
import { KeyboardKeys as keys } from 'uiSrc/constants/keys'
3-
import { useDispatch, useSelector } from 'react-redux'
3+
import { useAppDispatch, useAppSelector } from 'uiSrc/slices/hooks'
44

55
import { Nullable, scrollIntoView } from 'uiSrc/utils'
66
import { isModifiedEvent } from 'uiSrc/services'
@@ -37,14 +37,14 @@ const CliBody = (props: Props) => {
3737
const [commandTabPos, setCommandTabPos] = useState<number>(commandTabPosInit)
3838
const [wordsTyped, setWordsTyped] = useState<number>(0)
3939
const [matchingCmds, setMatchingCmds] = useState<string[]>([])
40-
const { loading: settingsLoading } = useSelector(cliSettingsSelector)
40+
const { loading: settingsLoading } = useAppSelector(cliSettingsSelector)
4141
const { loading, commandHistory: commandHistoryStore } =
42-
useSelector(outputSelector)
43-
const { commandsArray } = useSelector(appRedisCommandsSelector)
42+
useAppSelector(outputSelector)
43+
const { commandsArray } = useAppSelector(appRedisCommandsSelector)
4444

4545
const timerClickRef = useRef<NodeJS.Timeout>()
4646
const scrollDivRef: Ref<HTMLDivElement> = useRef(null)
47-
const dispatch = useDispatch()
47+
const dispatch = useAppDispatch()
4848

4949
useEffect(() => {
5050
inputEl?.focus()

redisinsight/ui/src/components/cli/components/cli-body/CliBodyWrapper.spec.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react'
22
import { cloneDeep, first } from 'lodash'
3-
import { useSelector } from 'react-redux'
3+
import { useAppSelector } from 'uiSrc/slices/hooks'
44
import {
55
cleanup,
66
fireEvent,
@@ -88,16 +88,16 @@ jest.mock('uiSrc/utils/cliHelper', () => ({
8888
const unsupportedCommands = ['sync', 'subscription']
8989
const cliCommandTestId = 'cli-command'
9090

91-
jest.mock('react-redux', () => ({
92-
...jest.requireActual('react-redux'),
93-
useSelector: jest.fn(),
91+
jest.mock('uiSrc/slices/hooks', () => ({
92+
...jest.requireActual('uiSrc/slices/hooks'),
93+
useAppSelector: jest.fn(),
9494
}))
9595

9696
describe('CliBodyWrapper', () => {
9797
beforeEach(() => {
9898
const state: any = store.getState()
9999

100-
;(useSelector as jest.Mock).mockImplementation(
100+
;(useAppSelector as jest.Mock).mockImplementation(
101101
(callback: (arg0: any) => any) =>
102102
callback({
103103
...state,

redisinsight/ui/src/components/cli/components/cli-body/CliBodyWrapper.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { decode } from 'html-entities'
22
import React, { useEffect, useState } from 'react'
3-
import { useSelector, useDispatch } from 'react-redux'
3+
import { useAppSelector, useAppDispatch } from 'uiSrc/slices/hooks'
44
import { useHotkeys } from 'react-hotkeys-hook'
55
import { useHistory, useParams } from 'react-router-dom'
66

@@ -58,21 +58,21 @@ const CliBodyWrapper = () => {
5858
const [command, setCommand] = useState('')
5959

6060
const history = useHistory()
61-
const dispatch = useDispatch()
61+
const dispatch = useAppDispatch()
6262
const { instanceId = '' } = useParams<{ instanceId: string }>()
63-
const { data = [] } = useSelector(outputSelector)
63+
const { data = [] } = useAppSelector(outputSelector)
6464
const {
6565
errorClient: error,
6666
unsupportedCommands,
6767
isEnteringCommand,
6868
isSearching,
6969
matchedCommand,
7070
cliClientUuid,
71-
} = useSelector(cliSettingsSelector)
72-
const { connectionType, host, port, db, name } = useSelector(
71+
} = useAppSelector(cliSettingsSelector)
72+
const { connectionType, host, port, db, name } = useAppSelector(
7373
connectedInstanceSelector,
7474
)
75-
const { db: currentDbIndex } = useSelector(outputSelector)
75+
const { db: currentDbIndex } = useAppSelector(outputSelector)
7676
const { isDangerousCommand } = useDatabaseEnvironment()
7777
const { requestConfirmation } = useProductionWriteConfirmation()
7878

0 commit comments

Comments
 (0)