diff --git a/src/app/App.tsx b/src/app/App.tsx index bb9d4022..dfb425ae 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -5,29 +5,46 @@ import { useEffect, useRef } from 'react'; import { Route, Routes, useLocation } from 'react-router-dom'; import { AppUpdateBanner } from '../common/AppUpdateBanner'; import { NotFound } from '../common/NotFound'; +import type { FCWithDeps } from '../container/utils'; +import { componentFactory, useDependencies } from '../container/utils'; import type { ServersMap } from '../servers/data'; import type { AppSettings } from '../settings/reducers/settings'; import { forceUpdate } from '../utils/helpers/sw'; import './App.scss'; -interface AppProps { +type AppProps = { fetchServers: () => void; servers: ServersMap; settings: AppSettings; resetAppUpdate: () => void; appUpdated: boolean; -} +}; + +type AppDependencies = { + MainHeader: FC; + Home: FC; + ShlinkWebComponentContainer: FC; + CreateServer: FC; + EditServer: FC; + Settings: FC; + ManageServers: FC; + ShlinkVersionsContainer: FC; +}; + +const App: FCWithDeps = ( + { fetchServers, servers, settings, appUpdated, resetAppUpdate }, +) => { + const { + MainHeader, + Home, + ShlinkWebComponentContainer, + CreateServer, + EditServer, + Settings, + ManageServers, + ShlinkVersionsContainer, + } = useDependencies(App); -export const App = ( - MainHeader: FC, - Home: FC, - ShlinkWebComponentContainer: FC, - CreateServer: FC, - EditServer: FC, - SettingsComp: FC, - ManageServers: FC, - ShlinkVersionsContainer: FC, -) => ({ fetchServers, servers, settings, appUpdated, resetAppUpdate }: AppProps) => { const location = useLocation(); const initialServers = useRef(servers); const isHome = location.pathname === '/'; @@ -52,7 +69,7 @@ export const App = (
} /> - } /> + } /> } /> } /> } /> @@ -70,3 +87,14 @@ export const App = (
); }; + +export const AppFactory = componentFactory(App, [ + 'MainHeader', + 'Home', + 'ShlinkWebComponentContainer', + 'CreateServer', + 'EditServer', + 'Settings', + 'ManageServers', + 'ShlinkVersionsContainer', +]); diff --git a/src/app/services/provideServices.ts b/src/app/services/provideServices.ts index 3ef5cd72..ab975037 100644 --- a/src/app/services/provideServices.ts +++ b/src/app/services/provideServices.ts @@ -1,22 +1,11 @@ import type Bottle from 'bottlejs'; import type { ConnectDecorator } from '../../container/types'; -import { App } from '../App'; +import { AppFactory } from '../App'; import { appUpdateAvailable, resetAppUpdate } from '../reducers/appUpdates'; export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Components - bottle.serviceFactory( - 'App', - App, - 'MainHeader', - 'Home', - 'ShlinkWebComponentContainer', - 'CreateServer', - 'EditServer', - 'Settings', - 'ManageServers', - 'ShlinkVersionsContainer', - ); + bottle.factory('App', AppFactory); bottle.decorator('App', connect(['servers', 'settings', 'appUpdated'], ['fetchServers', 'resetAppUpdate'])); // Actions diff --git a/src/container/utils.ts b/src/container/utils.ts new file mode 100644 index 00000000..67768379 --- /dev/null +++ b/src/container/utils.ts @@ -0,0 +1,29 @@ +import type { IContainer } from 'bottlejs'; +import type { FC } from 'react'; +import { useRef } from 'react'; + +export type FCWithDeps = FC & Partial; + +export function useDependencies(obj: Deps): Omit, keyof FC> { + const depsRef = useRef(obj as Omit, keyof FC>); + return depsRef.current; +} + +export function componentFactory, keyof FC>>( + Component: CompType, + deps: ReadonlyArray, +) { + return (container: IContainer, console = globalThis.console) => { + deps.forEach((dep) => { + const resolvedDependency = container[dep as string]; + if (!resolvedDependency && process.env.NODE_ENV !== 'production') { + console.error(`[Debug] Could not find "${dep as string}" dependency in container`); + } + + // eslint-disable-next-line no-param-reassign + Component[dep] = resolvedDependency; + }); + + return Component; + }; +} diff --git a/test/app/App.test.tsx b/test/app/App.test.tsx index 34963f1d..2466c068 100644 --- a/test/app/App.test.tsx +++ b/test/app/App.test.tsx @@ -2,18 +2,20 @@ import { render, screen } from '@testing-library/react'; import { fromPartial } from '@total-typescript/shoehorn'; import { createMemoryHistory } from 'history'; import { Router } from 'react-router-dom'; -import { App as createApp } from '../../src/app/App'; +import { AppFactory } from '../../src/app/App'; describe('', () => { - const App = createApp( - () => <>MainHeader, - () => <>Home, - () => <>ShlinkWebComponentContainer, - () => <>CreateServer, - () => <>EditServer, - () => <>SettingsComp, - () => <>ManageServers, - () => <>ShlinkVersions, + const App = AppFactory( + fromPartial({ + MainHeader: () => <>MainHeader, + Home: () => <>Home, + ShlinkWebComponentContainer: () => <>ShlinkWebComponentContainer, + CreateServer: () => <>CreateServer, + EditServer: () => <>EditServer, + Settings: () => <>SettingsComp, + ManageServers: () => <>ManageServers, + ShlinkVersionsContainer: () => <>ShlinkVersions, + }), ); const setUp = (activeRoute = '/') => { const history = createMemoryHistory();