diff --git a/src/common/services/provideServices.ts b/src/common/services/provideServices.ts index 7e367137..e27e38c5 100644 --- a/src/common/services/provideServices.ts +++ b/src/common/services/provideServices.ts @@ -23,7 +23,7 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('Home', () => Home); bottle.decorator('Home', withoutSelectedServer); - bottle.decorator('Home', connect(['servers'], ['resetSelectedServer'])); + bottle.decorator('Home', connect(['servers'], [])); bottle.factory('ShlinkWebComponentContainer', ShlinkWebComponentContainerFactory); bottle.decorator('ShlinkWebComponentContainer', connect(['selectedServer', 'settings'], ['selectServer'])); diff --git a/src/container/store.ts b/src/container/store.ts index f2d738ef..ff71085c 100644 --- a/src/container/store.ts +++ b/src/container/store.ts @@ -1,11 +1,11 @@ import { configureStore } from '@reduxjs/toolkit'; +import { useDispatch, useSelector } from 'react-redux'; import type { RLSOptions } from 'redux-localstorage-simple'; import { load, save } from 'redux-localstorage-simple'; import { initReducers } from '../reducers'; import { migrateDeprecatedSettings } from '../settings/helpers'; import type { ShlinkState } from './types'; -const isProduction = process.env.NODE_ENV === 'production'; const localStorageConfig: RLSOptions = { states: ['settings', 'servers'], namespace: 'shlink', @@ -14,6 +14,7 @@ const localStorageConfig: RLSOptions = { }; const getStateFromLocalStorage = () => migrateDeprecatedSettings(load(localStorageConfig) as ShlinkState); +const isProduction = process.env.NODE_ENV === 'production'; export const setUpStore = (preloadedState = getStateFromLocalStorage()) => configureStore({ devTools: !isProduction, reducer: initReducers(), @@ -22,3 +23,11 @@ export const setUpStore = (preloadedState = getStateFromLocalStorage()) => confi defaultMiddlewaresIncludingReduxThunk({ immutableCheck: false, serializableCheck: false }) // State is too big for these .concat(save(localStorageConfig)), }); + +export type StoreType = ReturnType; +export type AppDispatch = StoreType['dispatch']; +export type RootState = ReturnType; + +// Typed versions of useDispatch() and useSelector() +export const useAppDispatch = useDispatch.withTypes(); +export const useAppSelector = useSelector.withTypes(); diff --git a/src/container/types.ts b/src/container/types.ts index 487094db..46a42482 100644 --- a/src/container/types.ts +++ b/src/container/types.ts @@ -1,13 +1,15 @@ import type { Settings } from '@shlinkio/shlink-web-component/settings'; import type { SelectedServer, ServersMap } from '../servers/data'; -export interface ShlinkState { +/** Deprecated Use RootState */ +export type ShlinkState = { servers: ServersMap; selectedServer: SelectedServer; settings: Settings; appUpdated: boolean; -} +}; export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any; +/** @deprecated */ export type GetState = () => ShlinkState; diff --git a/src/servers/helpers/withoutSelectedServer.tsx b/src/servers/helpers/withoutSelectedServer.tsx index 5f533dc0..34c30a69 100644 --- a/src/servers/helpers/withoutSelectedServer.tsx +++ b/src/servers/helpers/withoutSelectedServer.tsx @@ -1,13 +1,10 @@ import type { FC } from 'react'; import { useEffect } from 'react'; +import { useSelectedServer } from '../reducers/selectedServer'; -interface WithoutSelectedServerProps { - resetSelectedServer: () => unknown; -} - -export function withoutSelectedServer(WrappedComponent: FC) { - return (props: WithoutSelectedServerProps & T) => { - const { resetSelectedServer } = props; +export function withoutSelectedServer(WrappedComponent: FC) { + return (props: T) => { + const { resetSelectedServer } = useSelectedServer(); useEffect(() => { resetSelectedServer(); }, [resetSelectedServer]); diff --git a/src/servers/reducers/selectedServer.ts b/src/servers/reducers/selectedServer.ts index 9e274fa5..ee562a0c 100644 --- a/src/servers/reducers/selectedServer.ts +++ b/src/servers/reducers/selectedServer.ts @@ -1,7 +1,9 @@ import { createAction, createSlice } from '@reduxjs/toolkit'; import { memoizeWith } from '@shlinkio/data-manipulation'; import type { ShlinkHealth } from '@shlinkio/shlink-web-component/api-contract'; +import { useCallback } from 'react'; import type { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder'; +import { useAppDispatch, useAppSelector } from '../../container/store'; import { createAsyncThunk } from '../../utils/helpers/redux'; import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version'; import type { SelectedServer, ServerWithId } from '../data'; @@ -72,3 +74,19 @@ const { reducer } = createSlice({ }); export const selectedServerReducer = reducer; + +export const useSelectedServer = () => { + const dispatch = useAppDispatch(); + const dispatchResetSelectedServer = useCallback(() => dispatch(resetSelectedServer()), [dispatch]); + const dispatchSelectServer = useCallback( + (options: SelectServerOptions) => dispatch(selectServer(options)), + [dispatch], + ); + const selectedServer = useAppSelector(({ selectedServer }) => selectedServer); + + return { + selectedServer, + resetSelectedServer: dispatchResetSelectedServer, + selectServer: dispatchSelectServer, + }; +}; diff --git a/src/servers/services/provideServices.ts b/src/servers/services/provideServices.ts index 71ed54c9..79e0ba3c 100644 --- a/src/servers/services/provideServices.ts +++ b/src/servers/services/provideServices.ts @@ -11,7 +11,7 @@ import { ManageServersFactory } from '../ManageServers'; import { ManageServersRowFactory } from '../ManageServersRow'; import { ManageServersRowDropdownFactory } from '../ManageServersRowDropdown'; import { fetchServers } from '../reducers/remoteServers'; -import { resetSelectedServer, selectServer } from '../reducers/selectedServer'; +import { selectServer } from '../reducers/selectedServer'; import { createServers, deleteServer, editServer, setAutoConnect } from '../reducers/servers'; import { ServersDropdown } from '../ServersDropdown'; import { ServersExporter } from './ServersExporter'; @@ -21,7 +21,7 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components bottle.factory('ManageServers', ManageServersFactory); bottle.decorator('ManageServers', withoutSelectedServer); - bottle.decorator('ManageServers', connect(['selectedServer', 'servers'], ['resetSelectedServer'])); + bottle.decorator('ManageServers', connect(['selectedServer', 'servers'], [])); bottle.factory('ManageServersRow', ManageServersRowFactory); @@ -30,10 +30,10 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.factory('CreateServer', CreateServerFactory); bottle.decorator('CreateServer', withoutSelectedServer); - bottle.decorator('CreateServer', connect(['selectedServer', 'servers'], ['createServers', 'resetSelectedServer'])); + bottle.decorator('CreateServer', connect(['selectedServer', 'servers'], ['createServers'])); bottle.factory('EditServer', EditServerFactory); - bottle.decorator('EditServer', connect(['selectedServer'], ['editServer', 'selectServer', 'resetSelectedServer'])); + bottle.decorator('EditServer', connect(['selectedServer'], ['editServer', 'selectServer'])); bottle.serviceFactory('ServersDropdown', () => ServersDropdown); bottle.decorator('ServersDropdown', connect(['servers', 'selectedServer'])); @@ -60,6 +60,4 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('editServer', () => editServer); bottle.serviceFactory('setAutoConnect', () => setAutoConnect); bottle.serviceFactory('fetchServers', fetchServers, 'HttpClient'); - - bottle.serviceFactory('resetSelectedServer', () => resetSelectedServer); };