mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-03 14:57:22 +03:00
Change how components get dependencies injected to avoid callback nesting
This commit is contained in:
parent
4677c24242
commit
046f79270a
4 changed files with 84 additions and 36 deletions
|
@ -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<AppProps, AppDependencies> = (
|
||||
{ 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 = (
|
|||
<div className={classNames('shlink-wrapper', { 'd-flex d-md-block align-items-center': isHome })}>
|
||||
<Routes>
|
||||
<Route index element={<Home />} />
|
||||
<Route path="/settings/*" element={<SettingsComp />} />
|
||||
<Route path="/settings/*" element={<Settings />} />
|
||||
<Route path="/manage-servers" element={<ManageServers />} />
|
||||
<Route path="/server/create" element={<CreateServer />} />
|
||||
<Route path="/server/:serverId/edit" element={<EditServer />} />
|
||||
|
@ -70,3 +87,14 @@ export const App = (
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AppFactory = componentFactory(App, [
|
||||
'MainHeader',
|
||||
'Home',
|
||||
'ShlinkWebComponentContainer',
|
||||
'CreateServer',
|
||||
'EditServer',
|
||||
'Settings',
|
||||
'ManageServers',
|
||||
'ShlinkVersionsContainer',
|
||||
]);
|
||||
|
|
|
@ -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
|
||||
|
|
29
src/container/utils.ts
Normal file
29
src/container/utils.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import type { IContainer } from 'bottlejs';
|
||||
import type { FC } from 'react';
|
||||
import { useRef } from 'react';
|
||||
|
||||
export type FCWithDeps<Props, Deps> = FC<Props> & Partial<Deps>;
|
||||
|
||||
export function useDependencies<Deps>(obj: Deps): Omit<Required<Deps>, keyof FC> {
|
||||
const depsRef = useRef(obj as Omit<Required<Deps>, keyof FC>);
|
||||
return depsRef.current;
|
||||
}
|
||||
|
||||
export function componentFactory<Deps, CompType = Omit<Partial<Deps>, keyof FC>>(
|
||||
Component: CompType,
|
||||
deps: ReadonlyArray<keyof CompType>,
|
||||
) {
|
||||
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;
|
||||
};
|
||||
}
|
|
@ -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('<App />', () => {
|
||||
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();
|
||||
|
|
Loading…
Reference in a new issue