diff --git a/src/container/store.ts b/src/container/store.ts index 7af11755..cf48b3c2 100644 --- a/src/container/store.ts +++ b/src/container/store.ts @@ -18,7 +18,8 @@ export const setUpStore = (container: IContainer) => configureStore({ devTools: !isProduction, reducer: reducer(container), preloadedState, - middleware: (defaultMiddlewaresIncludingReduxThunk) => defaultMiddlewaresIncludingReduxThunk( - { immutableCheck: false, serializableCheck: false }, // State is too big for these - ).prepend(container.selectServerListener.middleware).concat(save(localStorageConfig)), + middleware: (defaultMiddlewaresIncludingReduxThunk) => + defaultMiddlewaresIncludingReduxThunk({ immutableCheck: false, serializableCheck: false })// State is too big for these + .prepend(container.selectServerListener.middleware) + .concat(save(localStorageConfig)), }); diff --git a/src/reducers/index.ts b/src/reducers/index.ts index 6d0d71fd..553933c1 100644 --- a/src/reducers/index.ts +++ b/src/reducers/index.ts @@ -1,7 +1,6 @@ import { IContainer } from 'bottlejs'; import { combineReducers } from '@reduxjs/toolkit'; import { serversReducer } from '../servers/reducers/servers'; -import selectedServerReducer from '../servers/reducers/selectedServer'; import shortUrlVisitsReducer from '../visits/reducers/shortUrlVisits'; import tagVisitsReducer from '../visits/reducers/tagVisits'; import domainVisitsReducer from '../visits/reducers/domainVisits'; @@ -15,7 +14,7 @@ import { ShlinkState } from '../container/types'; export default (container: IContainer) => combineReducers({ servers: serversReducer, - selectedServer: selectedServerReducer, + selectedServer: container.selectedServerReducer, shortUrlsList: container.shortUrlsListReducer, shortUrlCreation: container.shortUrlCreationReducer, shortUrlDeletion: container.shortUrlDeletionReducer, diff --git a/src/servers/reducers/selectedServer.ts b/src/servers/reducers/selectedServer.ts index f6ef4500..3415a15c 100644 --- a/src/servers/reducers/selectedServer.ts +++ b/src/servers/reducers/selectedServer.ts @@ -1,20 +1,17 @@ -import { createAction, createListenerMiddleware, PayloadAction } from '@reduxjs/toolkit'; +import { createAction, createListenerMiddleware, createSlice, PayloadAction } from '@reduxjs/toolkit'; import { identity, memoizeWith, pipe } from 'ramda'; import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version'; import { isReachableServer, SelectedServer } from '../data'; import { ShlinkHealth } from '../../api/types'; -import { buildReducer, createAsyncThunk } from '../../utils/helpers/redux'; +import { createAsyncThunk } from '../../utils/helpers/redux'; import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder'; -export const SELECT_SERVER = 'shlink/selectedServer/selectServer'; -export const RESET_SELECTED_SERVER = 'shlink/selectedServer/resetSelectedServer'; +const REDUCER_PREFIX = 'shlink/selectedServer'; export const MIN_FALLBACK_VERSION = '1.0.0'; export const MAX_FALLBACK_VERSION = '999.999.999'; export const LATEST_VERSION_CONSTRAINT = 'latest'; -export type SelectServerAction = PayloadAction; - const versionToSemVer = pipe( (version: string) => (version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version), toSemVer(MIN_FALLBACK_VERSION), @@ -30,42 +27,39 @@ const getServerVersion = memoizeWith( const initialState: SelectedServer = null; -export default buildReducer({ - [RESET_SELECTED_SERVER]: () => initialState, - [SELECT_SERVER]: (_, { payload }) => payload, - [`${SELECT_SERVER}/fulfilled`]: (_, { payload }) => payload, -}, initialState); +export const resetSelectedServer = createAction(`${REDUCER_PREFIX}/resetSelectedServer`); -export const resetSelectedServer = createAction(RESET_SELECTED_SERVER); +export const selectServer = (buildShlinkApiClient: ShlinkApiClientBuilder) => createAsyncThunk( + `${REDUCER_PREFIX}/selectServer`, + async (serverId: string, { dispatch, getState }): Promise => { + dispatch(resetSelectedServer()); -export const selectServer = ( - buildShlinkApiClient: ShlinkApiClientBuilder, -) => createAsyncThunk(SELECT_SERVER, async (serverId: string, { dispatch, getState }): Promise => { - dispatch(resetSelectedServer()); + const { servers } = getState(); + const selectedServer = servers[serverId]; - const { servers } = getState(); - const selectedServer = servers[serverId]; + if (!selectedServer) { + return { serverNotFound: true }; + } - if (!selectedServer) { - return { serverNotFound: true }; - } + try { + const { health } = buildShlinkApiClient(selectedServer); + const { version, printableVersion } = await getServerVersion(serverId, health); - try { - const { health } = buildShlinkApiClient(selectedServer); - const { version, printableVersion } = await getServerVersion(serverId, health); + return { + ...selectedServer, + version, + printableVersion, + }; + } catch (e) { + return { ...selectedServer, serverNotReachable: true }; + } + }, +); - return { - ...selectedServer, - version, - printableVersion, - }; - } catch (e) { - return { ...selectedServer, serverNotReachable: true }; - } -}); +type SelectServerThunk = ReturnType; export const selectServerListener = ( - selectServerThunk: ReturnType, + selectServerThunk: SelectServerThunk, loadMercureInfo: () => PayloadAction, // TODO Consider setting actual type, if relevant ) => { const listener = createListenerMiddleware(); @@ -79,3 +73,13 @@ export const selectServerListener = ( return listener; }; + +export const selectedServerReducerCreator = (selectServerThunk: SelectServerThunk) => createSlice({ + name: REDUCER_PREFIX, + initialState, + reducers: {}, + extraReducers: (builder) => { + builder.addCase(resetSelectedServer, () => initialState); + builder.addCase(selectServerThunk.fulfilled, (_, { payload }) => payload as any); + }, +}); diff --git a/src/servers/services/provideServices.ts b/src/servers/services/provideServices.ts index e9acf638..842f931a 100644 --- a/src/servers/services/provideServices.ts +++ b/src/servers/services/provideServices.ts @@ -1,3 +1,4 @@ +import { prop } from 'ramda'; import Bottle from 'bottlejs'; import { CreateServer } from '../CreateServer'; import { ServersDropdown } from '../ServersDropdown'; @@ -5,7 +6,12 @@ import { DeleteServerModal } from '../DeleteServerModal'; import { DeleteServerButton } from '../DeleteServerButton'; import { EditServer } from '../EditServer'; import { ImportServersBtn } from '../helpers/ImportServersBtn'; -import { resetSelectedServer, selectServer, selectServerListener } from '../reducers/selectedServer'; +import { + resetSelectedServer, + selectedServerReducerCreator, + selectServer, + selectServerListener, +} from '../reducers/selectedServer'; import { createServers, deleteServer, editServer, setAutoConnect } from '../reducers/servers'; import { fetchServers } from '../reducers/remoteServers'; import { ServerError } from '../helpers/ServerError'; @@ -80,6 +86,8 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Reducers bottle.serviceFactory('selectServerListener', selectServerListener, 'selectServer', 'loadMercureInfo'); + bottle.serviceFactory('selectedServerReducerCreator', selectedServerReducerCreator, 'selectServer'); + bottle.serviceFactory('selectedServerReducer', prop('reducer'), 'selectedServerReducerCreator'); }; export default provideServices; diff --git a/test/servers/reducers/selectedServer.test.ts b/test/servers/reducers/selectedServer.test.ts index 313ab639..37fbbefc 100644 --- a/test/servers/reducers/selectedServer.test.ts +++ b/test/servers/reducers/selectedServer.test.ts @@ -1,8 +1,9 @@ import { v4 as uuid } from 'uuid'; import { Mock } from 'ts-mockery'; -import reducer, { +import { selectServer as selectServerCreator, resetSelectedServer, + selectedServerReducerCreator, MAX_FALLBACK_VERSION, MIN_FALLBACK_VERSION, } from '../../../src/servers/reducers/selectedServer'; @@ -14,6 +15,7 @@ describe('selectedServerReducer', () => { const health = jest.fn(); const buildApiClient = jest.fn().mockReturnValue(Mock.of({ health })); const selectServer = selectServerCreator(buildApiClient); + const { reducer } = selectedServerReducerCreator(selectServer); afterEach(jest.clearAllMocks);