Refactor DI approach for components

This commit is contained in:
Alejandro Celaya
2023-09-05 09:08:42 +02:00
parent 046f79270a
commit 6926afbac1
30 changed files with 371 additions and 234 deletions

View File

@@ -1,17 +1,19 @@
import { SimpleCard } from '@shlinkio/shlink-frontend-kit';
import type { ReactNode } from 'react';
import type { PropsWithChildren, ReactNode } from 'react';
import { Component } from 'react';
import { Button } from 'reactstrap';
interface ErrorHandlerState {
hasError: boolean;
}
type ErrorHandlerProps = PropsWithChildren<{
location?: typeof window.location;
console?: typeof window.console;
}>;
export const ErrorHandler = (
{ location }: Window,
{ error }: Console,
) => class extends Component<any, ErrorHandlerState> {
public constructor(props: object) {
type ErrorHandlerState = {
hasError: boolean;
};
export class ErrorHandler extends Component<ErrorHandlerProps, ErrorHandlerState> {
public constructor(props: ErrorHandlerProps) {
super(props);
this.state = { hasError: false };
}
@@ -21,13 +23,14 @@ export const ErrorHandler = (
}
public componentDidCatch(e: Error): void {
if (process.env.NODE_ENV !== 'development') {
error(e);
}
const { console = globalThis.console } = this.props;
console.error(e);
}
public render(): ReactNode {
const { hasError } = this.state;
const { location = globalThis.location } = this.props;
if (hasError) {
return (
<div className="home">
@@ -44,4 +47,4 @@ export const ErrorHandler = (
const { children } = this.props;
return children;
}
};
}

View File

@@ -6,10 +6,17 @@ import type { FC } from 'react';
import { useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import type { FCWithDeps } from '../container/utils';
import { componentFactory, useDependencies } from '../container/utils';
import { ShlinkLogo } from './img/ShlinkLogo';
import './MainHeader.scss';
export const MainHeader = (ServersDropdown: FC) => () => {
type MainHeaderDeps = {
ServersDropdown: FC;
};
const MainHeader: FCWithDeps<{}, MainHeaderDeps> = () => {
const { ServersDropdown } = useDependencies(MainHeader);
const [isNotCollapsed, toggleCollapse, , collapse] = useToggle();
const location = useLocation();
const { pathname } = location;
@@ -43,3 +50,5 @@ export const MainHeader = (ServersDropdown: FC) => () => {
</Navbar>
);
};
export const MainHeaderFactory = componentFactory(MainHeader, ['ServersDropdown']);

View File

@@ -2,7 +2,7 @@ import type { FC, PropsWithChildren } from 'react';
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
export const ScrollToTop: FC<PropsWithChildren<unknown>> = ({ children }) => {
export const ScrollToTop: FC<PropsWithChildren> = ({ children }) => {
const location = useLocation();
useEffect(() => {

View File

@@ -1,20 +1,34 @@
import type { Settings, ShlinkWebComponentType, TagColorsStorage } from '@shlinkio/shlink-web-component';
import type { FC } from 'react';
import type { ShlinkApiClientBuilder } from '../api/services/ShlinkApiClientBuilder';
import type { FCWithDeps } from '../container/utils';
import { componentFactory, useDependencies } from '../container/utils';
import { isReachableServer } from '../servers/data';
import type { WithSelectedServerProps } from '../servers/helpers/withSelectedServer';
import { withSelectedServer } from '../servers/helpers/withSelectedServer';
import { NotFound } from './NotFound';
interface ShlinkWebComponentContainerProps {
type ShlinkWebComponentContainerProps = WithSelectedServerProps & {
settings: Settings;
}
};
export const ShlinkWebComponentContainer = (
type ShlinkWebComponentContainerDeps = {
buildShlinkApiClient: ShlinkApiClientBuilder,
tagColorsStorage: TagColorsStorage,
TagColorsStorage: TagColorsStorage,
ShlinkWebComponent: ShlinkWebComponentType,
ServerError: FC,
) => withSelectedServer<ShlinkWebComponentContainerProps>(({ selectedServer, settings }) => {
};
const ShlinkWebComponentContainer: FCWithDeps<
ShlinkWebComponentContainerProps,
ShlinkWebComponentContainerDeps
> = withSelectedServer(({ selectedServer, settings }) => {
const {
buildShlinkApiClient,
TagColorsStorage: tagColorsStorage,
ShlinkWebComponent,
ServerError,
} = useDependencies(ShlinkWebComponentContainer);
const selectedServerIsReachable = isReachableServer(selectedServer);
const routesPrefix = selectedServerIsReachable ? `/server/${selectedServer.id}` : '';
@@ -34,4 +48,11 @@ export const ShlinkWebComponentContainer = (
)}
/>
);
}, ServerError);
});
export const ShlinkWebComponentContainerFactory = componentFactory(ShlinkWebComponentContainer, [
'buildShlinkApiClient',
'TagColorsStorage',
'ShlinkWebComponent',
'ServerError',
]);

View File

@@ -5,10 +5,10 @@ import type { ConnectDecorator } from '../../container/types';
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
import { ErrorHandler } from '../ErrorHandler';
import { Home } from '../Home';
import { MainHeader } from '../MainHeader';
import { MainHeaderFactory } from '../MainHeader';
import { ScrollToTop } from '../ScrollToTop';
import { ShlinkVersionsContainer } from '../ShlinkVersionsContainer';
import { ShlinkWebComponentContainer } from '../ShlinkWebComponentContainer';
import { ShlinkWebComponentContainerFactory } from '../ShlinkWebComponentContainer';
export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
// Services
@@ -20,25 +20,18 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
// Components
bottle.serviceFactory('ScrollToTop', () => ScrollToTop);
bottle.serviceFactory('MainHeader', MainHeader, 'ServersDropdown');
bottle.factory('MainHeader', MainHeaderFactory);
bottle.serviceFactory('Home', () => Home);
bottle.decorator('Home', withoutSelectedServer);
bottle.decorator('Home', connect(['servers'], ['resetSelectedServer']));
bottle.serviceFactory('ShlinkWebComponent', () => ShlinkWebComponent);
bottle.serviceFactory(
'ShlinkWebComponentContainer',
ShlinkWebComponentContainer,
'buildShlinkApiClient',
'TagColorsStorage',
'ShlinkWebComponent',
'ServerError',
);
bottle.factory('ShlinkWebComponentContainer', ShlinkWebComponentContainerFactory);
bottle.decorator('ShlinkWebComponentContainer', connect(['selectedServer', 'settings'], ['selectServer']));
bottle.serviceFactory('ShlinkVersionsContainer', () => ShlinkVersionsContainer);
bottle.decorator('ShlinkVersionsContainer', connect(['selectedServer']));
bottle.serviceFactory('ErrorHandler', ErrorHandler, 'window', 'console');
bottle.serviceFactory('ErrorHandler', () => ErrorHandler);
};