diff --git a/src/App.tsx b/src/App.tsx index 6e6b27d1..639b0790 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,6 @@ import { FC, Suspense, useEffect, useRef } from 'react'; import { Navigate, RouteObject, RouterProvider, createBrowserRouter } from 'react-router-dom'; -import { RecoilRoot, useSetRecoilState } from 'recoil'; +import { RecoilRoot } from 'recoil'; import { ErrorBoundary } from '@components/ErrorBoundary'; import { Loading } from '@components/Loading'; import { ROUTES } from '@common/constants/routes.constants'; @@ -8,10 +8,10 @@ import { DEFAULT_LOCALE } from '@common/constants/i18n.constants'; import { OKAPI_CONFIG } from '@common/constants/api.constants'; import { localStorageService } from '@common/services/storage'; import { Root, Search, EditWrapper, ExternalResourcePreview } from '@views'; -import state from '@state'; import en from '../translations/ui-linked-data/en.json'; import { AsyncIntlProvider, ServicesProvider } from './providers'; import './App.scss'; +import { useConfigState } from './store'; type IContainer = { routePrefix?: string; @@ -54,8 +54,7 @@ export const routes: RouteObject[] = [ const createRouter = (basename: string) => createBrowserRouter(routes, { basename }); const Container: FC = ({ routePrefix = '', config }) => { - const setCustomEvents = useSetRecoilState(state.config.customEvents); - const setHasNavigationOrigin = useSetRecoilState(state.config.hasNavigationOrigin); + const { setCustomEvents, setHasNavigationOrigin } = useConfigState(); const cachedMessages = useRef({ [DEFAULT_LOCALE]: en }); useEffect(() => { diff --git a/src/common/hooks/useContainerEvents.ts b/src/common/hooks/useContainerEvents.ts index e98bbf39..9cb5fc27 100644 --- a/src/common/hooks/useContainerEvents.ts +++ b/src/common/hooks/useContainerEvents.ts @@ -1,12 +1,9 @@ - import { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useRecoilValue } from 'recoil'; import { IS_EMBEDDED_MODE } from '@common/constants/build.constants'; import { dispatchEventWrapper, getWrapperAsWebComponent } from '@common/helpers/dom.helper'; import { ROUTES } from '@common/constants/routes.constants'; -import { useStatusState } from '@src/store'; -import state from '@state'; +import { useConfigState, useStatusState } from '@src/store'; type IUseContainerEvents = | { @@ -16,8 +13,9 @@ type IUseContainerEvents = | undefined; export const useContainerEvents = ({ onTriggerModal, watchEditedState = false }: IUseContainerEvents = {}) => { - const hasNavigationOrigin = useRecoilValue(state.config.hasNavigationOrigin); + const { hasNavigationOrigin } = useConfigState(); const { isEditedRecord: isEdited } = useStatusState(); + const { customEvents } = useConfigState(); const { BLOCK_NAVIGATION, UNBLOCK_NAVIGATION, @@ -25,7 +23,7 @@ export const useContainerEvents = ({ onTriggerModal, watchEditedState = false }: PROCEED_NAVIGATION, NAVIGATE_TO_ORIGIN, DROP_NAVIGATE_TO_ORIGIN, - } = useRecoilValue(state.config.customEvents) ?? {}; + } = customEvents ?? {}; const navigate = useNavigate(); useEffect(() => { diff --git a/src/providers/AsyncIntlProvider.tsx b/src/providers/AsyncIntlProvider.tsx index 67ba400f..c406a39a 100644 --- a/src/providers/AsyncIntlProvider.tsx +++ b/src/providers/AsyncIntlProvider.tsx @@ -1,9 +1,8 @@ import { FC } from 'react'; import { IntlProvider } from 'react-intl'; -import { useRecoilValue } from 'recoil'; import { useLoadI18nMessages } from '@common/hooks/useLoadI18nMessages'; import { DEFAULT_LOCALE } from '@common/constants/i18n.constants'; -import state from '@state'; +import { useConfigState } from '@src/store'; type AsyncIntlProviderProps = { cachedMessages: I18nMessages; @@ -11,7 +10,7 @@ type AsyncIntlProviderProps = { }; export const AsyncIntlProvider: FC = ({ cachedMessages, children }) => { - const locale = useRecoilValue(state.config.locale); + const { locale } = useConfigState(); const { getMessages } = useLoadI18nMessages(cachedMessages, DEFAULT_LOCALE); const i18nMessages = getMessages(locale); diff --git a/src/state/config.ts b/src/state/config.ts deleted file mode 100644 index 89d6be7d..00000000 --- a/src/state/config.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { OKAPI_CONFIG } from '@common/constants/api.constants'; -import { LOCALES } from '@common/i18n/locales'; -import { localStorageService } from '@common/services/storage'; -import { atom } from 'recoil'; - -const locale = atom<(typeof LOCALES)[keyof typeof LOCALES]>({ - key: 'config.locale', - default: localStorageService.deserialize(OKAPI_CONFIG)?.locale || LOCALES.ENGLISH_US, -}); - -const i18nMessages = atom({ - key: 'config.i18nMessages', - default: {}, -}); - -const customEvents = atom | null>({ - key: 'config.customEvents', - default: null, -}); - -const hasNavigationOrigin = atom({ - key: 'config.hasNavigationOrigin', - default: false, -}); - -export default { - locale, - i18nMessages, - customEvents, - hasNavigationOrigin, -}; diff --git a/src/state/index.ts b/src/state/index.ts index edb31a27..e13e40fb 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -1,9 +1,7 @@ -import config from './config'; import ui from './ui'; import search from './search'; export default { - config, ui, search, }; diff --git a/src/store/config.ts b/src/store/config.ts index e69de29b..64877708 100644 --- a/src/store/config.ts +++ b/src/store/config.ts @@ -0,0 +1,25 @@ +import { LOCALES } from '@common/i18n/locales'; +import { createBaseSlice, SliceState } from './utils/slice'; +import { generateStore, type StateCreatorTyped } from './utils/storeCreator'; +import { OKAPI_CONFIG } from '@common/constants/api.constants'; +import { localStorageService } from '@common/services/storage'; + +type Locale = (typeof LOCALES)[keyof typeof LOCALES]; +type CustomEvents = Record | null; + +export type ConfigState = SliceState<'locale', Locale> & + SliceState<'customEvents', CustomEvents> & + SliceState<'hasNavigationOrigin', boolean>; + +const STORE_NAME = 'Config'; + +const configStore: StateCreatorTyped = (...args) => ({ + ...createBaseSlice( + { basic: 'locale' }, + localStorageService.deserialize(OKAPI_CONFIG)?.locale || LOCALES.ENGLISH_US, + )(...args), + ...createBaseSlice({ basic: 'customEvents' }, null as CustomEvents)(...args), + ...createBaseSlice({ basic: 'hasNavigationOrigin' }, false)(...args), +}); + +export const useConfigStore = generateStore(configStore, STORE_NAME); diff --git a/src/store/index.ts b/src/store/index.ts index b54c556e..33942ea7 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -4,6 +4,7 @@ export * from './loadingState'; export * from './marcPreview'; export * from './profile'; export * from './inputs'; +export * from './config'; // Selector hooks export * from './selectors'; diff --git a/src/store/selectors.ts b/src/store/selectors.ts index de79865e..61896f82 100644 --- a/src/store/selectors.ts +++ b/src/store/selectors.ts @@ -4,9 +4,11 @@ import { useLoadingStateStore } from './loadingState'; import { useMarcPreviewStore } from './marcPreview'; import { useProfileStore } from './profile'; import { useInputsStore } from './inputs'; +import { useConfigStore } from './config'; export const useStatusState = () => createSelectors(useStatusStore).use; export const useLoadingState = () => createSelectors(useLoadingStateStore).use; export const useMarcPreviewState = () => createSelectors(useMarcPreviewStore).use; export const useProfileState = () => createSelectors(useProfileStore).use; export const useInputsState = () => createSelectors(useInputsStore).use; +export const useConfigState = () => createSelectors(useConfigStore).use; diff --git a/src/store/utils/slice.ts b/src/store/utils/slice.ts index f49344ea..1e7732d7 100644 --- a/src/store/utils/slice.ts +++ b/src/store/utils/slice.ts @@ -17,10 +17,6 @@ const capitalize = (value: string) => { }; const updateValue = (value: V, updatedValue: T): V => { - console.log('===================================='); - console.log('value', value, 'updatedValue', updatedValue); - console.log('===================================='); - if (Array.isArray(value)) { return [...value, updatedValue] as any; } else if (value instanceof Map) { diff --git a/src/test/__tests__/common/hooks/useContainerEvents.test.ts b/src/test/__tests__/common/hooks/useContainerEvents.test.ts index 9c7259b5..f1a27ca1 100644 --- a/src/test/__tests__/common/hooks/useContainerEvents.test.ts +++ b/src/test/__tests__/common/hooks/useContainerEvents.test.ts @@ -1,8 +1,7 @@ import { renderHook } from '@testing-library/react'; -import { useRecoilValue } from 'recoil'; import { useContainerEvents } from '@common/hooks/useContainerEvents'; import * as domHelper from '@common/helpers/dom.helper'; -import { useStatusStore } from '@src/store'; +import { useConfigStore, useStatusStore } from '@src/store'; import { setInitialGlobalState } from '@src/test/__mocks__/store'; jest.mock('react-router-dom', () => ({ @@ -15,20 +14,21 @@ const mockEvents = { BLOCK_NAVIGATION: 'mockBlock', UNBLOCK_NAVIGATION: 'mockUnblock', }; -jest.mock('recoil'); jest.mock('@common/constants/build.constants', () => ({ IS_EMBEDDED_MODE: true })); describe('useContainerEvents', () => { const renderUseContainerEventsHook = (isEditedRecord: boolean) => { (domHelper.dispatchEventWrapper as jest.Mock) = mockDispatchEventWrapper; - (useRecoilValue as jest.Mock).mockReturnValueOnce(false); - (useRecoilValue as jest.Mock).mockReturnValueOnce(mockEvents); setInitialGlobalState([ { store: useStatusStore, state: { isEditedRecord }, }, + { + store: useConfigStore, + state: { customEvents: mockEvents }, + }, ]); renderHook(() => useContainerEvents({ watchEditedState: true })); diff --git a/src/test/__tests__/components/Prompt.test.tsx b/src/test/__tests__/components/Prompt.test.tsx index 90d91f35..2e0eda7e 100644 --- a/src/test/__tests__/components/Prompt.test.tsx +++ b/src/test/__tests__/components/Prompt.test.tsx @@ -3,7 +3,8 @@ import { Prompt } from '@components/Prompt'; import { RouterProvider, createMemoryRouter } from 'react-router-dom'; import { createModalContainer } from '@src/test/__mocks__/common/misc/createModalContainer.mock'; import { RecoilRoot } from 'recoil'; -import state from '@state'; +import { setInitialGlobalState } from '@src/test/__mocks__/store'; +import { useConfigStore } from '@src/store'; const mockedUseBlocker = { reset: jest.fn(), @@ -49,11 +50,16 @@ jest.mock('@common/helpers/dom.helper', () => ({ const mockPath = '/resources/:resourceId/edit'; -const renderPrompt = (isBlocking = true) => - render( - snapshot.set(state.config.customEvents, { TRIGGER_MODAL: 'triggermodal' })} - > +const renderPrompt = (isBlocking = true) => { + setInitialGlobalState([ + { + store: useConfigStore, + state: { customEvents: { TRIGGER_MODAL: 'triggermodal' } }, + }, + ]); + + return render( + }], { initialEntries: [mockPath], @@ -61,6 +67,7 @@ const renderPrompt = (isBlocking = true) => /> , ); +}; describe('Prompt', () => { beforeAll(() => {