Fix react-hooks/exhaustive-deps in ImportServersBtn

This commit is contained in:
Alejandro Celaya 2023-09-02 19:43:41 +02:00
parent fbc47846e3
commit a11a2c84fe
4 changed files with 40 additions and 45 deletions

View file

@ -33,7 +33,7 @@ export const DuplicatedServersModal: FC<DuplicatedServersModalProps> = (
</span> </span>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color="link" onClick={onDiscard}>{hasMultipleServers ? 'Ignore duplicated' : 'Discard'}</Button> <Button color="link" onClick={onDiscard}>{hasMultipleServers ? 'Ignore duplicates' : 'Discard'}</Button>
<Button color="primary" onClick={onSave}>Save anyway</Button> <Button color="primary" onClick={onSave}>Save anyway</Button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>

View file

@ -1,5 +0,0 @@
.import-servers-btn__csv-select {
position: absolute;
left: -9999px;
top: -9999px;
}

View file

@ -1,14 +1,13 @@
import { faFileUpload as importIcon } from '@fortawesome/free-solid-svg-icons'; import { faFileUpload as importIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useElementRef, useToggle } from '@shlinkio/shlink-frontend-kit'; import { useElementRef, useToggle } from '@shlinkio/shlink-frontend-kit';
import { complement, pipe } from 'ramda'; import { complement } from 'ramda';
import type { ChangeEvent, FC, PropsWithChildren } from 'react'; import type { ChangeEvent, FC, PropsWithChildren } from 'react';
import { useEffect, useState } from 'react'; import { useCallback, useRef, useState } from 'react';
import { Button, UncontrolledTooltip } from 'reactstrap'; import { Button, UncontrolledTooltip } from 'reactstrap';
import type { ServerData, ServersMap } from '../data'; import type { ServerData, ServersMap } from '../data';
import type { ServersImporter } from '../services/ServersImporter'; import type { ServersImporter } from '../services/ServersImporter';
import { DuplicatedServersModal } from './DuplicatedServersModal'; import { DuplicatedServersModal } from './DuplicatedServersModal';
import './ImportServersBtn.scss';
export type ImportServersBtnProps = PropsWithChildren<{ export type ImportServersBtnProps = PropsWithChildren<{
onImport?: () => void; onImport?: () => void;
@ -25,7 +24,7 @@ interface ImportServersBtnConnectProps extends ImportServersBtnProps {
const serversFiltering = (servers: ServerData[]) => const serversFiltering = (servers: ServerData[]) =>
({ url, apiKey }: ServerData) => servers.some((server) => server.url === url && server.apiKey === apiKey); ({ url, apiKey }: ServerData) => servers.some((server) => server.url === url && server.apiKey === apiKey);
export const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC<ImportServersBtnConnectProps> => ({ export const ImportServersBtn = (serversImporter: ServersImporter): FC<ImportServersBtnConnectProps> => ({
createServers, createServers,
servers, servers,
children, children,
@ -35,36 +34,43 @@ export const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC
className = '', className = '',
}) => { }) => {
const ref = useElementRef<HTMLInputElement>(); const ref = useElementRef<HTMLInputElement>();
const [serversToCreate, setServersToCreate] = useState<ServerData[] | undefined>();
const [duplicatedServers, setDuplicatedServers] = useState<ServerData[]>([]); const [duplicatedServers, setDuplicatedServers] = useState<ServerData[]>([]);
const [isModalOpen,, showModal, hideModal] = useToggle(); const [isModalOpen,, showModal, hideModal] = useToggle();
const create = pipe(createServers, onImport);
const createAllServers = pipe(() => create(serversToCreate ?? []), hideModal); const serversToCreate = useRef<ServerData[]>([]);
const createNonDuplicatedServers = pipe( const create = useCallback((serversData: ServerData[]) => {
() => create((serversToCreate ?? []).filter(complement(serversFiltering(duplicatedServers)))), createServers(serversData);
hideModal, onImport();
}, [createServers, onImport]);
const onFile = useCallback(
async ({ target }: ChangeEvent<HTMLInputElement>) =>
serversImporter.importServersFromFile(target.files?.[0])
.then((newServers) => {
serversToCreate.current = newServers;
const existingServers = Object.values(servers);
const dupServers = newServers.filter(serversFiltering(existingServers));
const hasDuplicatedServers = !!dupServers.length;
!hasDuplicatedServers ? create(newServers) : setDuplicatedServers(dupServers);
hasDuplicatedServers && showModal();
})
.then(() => {
// Reset input after processing file
(target as { value: string | null }).value = null; // eslint-disable-line no-param-reassign
})
.catch(onImportError),
[create, onImportError, servers, showModal],
); );
const onFile = async ({ target }: ChangeEvent<HTMLInputElement>) =>
importServersFromFile(target.files?.[0])
.then(setServersToCreate)
.then(() => {
// Reset input after processing file
(target as { value: string | null }).value = null; // eslint-disable-line no-param-reassign
})
.catch(onImportError);
useEffect(() => { const createAllServers = useCallback(() => {
if (!serversToCreate) { create(serversToCreate.current);
return; hideModal();
} }, [create, hideModal, serversToCreate]);
const createNonDuplicatedServers = () => {
const existingServers = Object.values(servers); create(serversToCreate.current.filter(complement(serversFiltering(duplicatedServers))));
const dupServers = serversToCreate.filter(serversFiltering(existingServers)); hideModal();
const hasDuplicatedServers = !!dupServers.length; };
!hasDuplicatedServers ? create(serversToCreate) : setDuplicatedServers(dupServers);
hasDuplicatedServers && showModal();
}, [serversToCreate]);
return ( return (
<> <>
@ -72,16 +78,10 @@ export const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC
<FontAwesomeIcon icon={importIcon} fixedWidth /> {children ?? 'Import from file'} <FontAwesomeIcon icon={importIcon} fixedWidth /> {children ?? 'Import from file'}
</Button> </Button>
<UncontrolledTooltip placement={tooltipPlacement} target="importBtn"> <UncontrolledTooltip placement={tooltipPlacement} target="importBtn">
You can create servers by importing a CSV file with columns <b>name</b>, <b>apiKey</b> and <b>url</b>. You can create servers by importing a CSV file with <b>name</b>, <b>apiKey</b> and <b>url</b> columns.
</UncontrolledTooltip> </UncontrolledTooltip>
<input <input type="file" accept="text/csv" className="d-none" ref={ref} onChange={onFile} />
type="file"
accept="text/csv"
className="import-servers-btn__csv-select"
ref={ref}
onChange={onFile}
/>
<DuplicatedServersModal <DuplicatedServersModal
isOpen={isModalOpen} isOpen={isModalOpen}

View file

@ -39,7 +39,7 @@ describe('<DuplicatedServersModal />', () => {
header: 'Duplicated servers', header: 'Duplicated servers',
firstParagraph: 'The next servers already exist:', firstParagraph: 'The next servers already exist:',
lastParagraph: 'Do you want to ignore duplicated servers?', lastParagraph: 'Do you want to ignore duplicated servers?',
discardBtn: 'Ignore duplicated', discardBtn: 'Ignore duplicates',
}, },
], ],
])('renders expected texts based on amount of servers', (duplicatedServers, assertions) => { ])('renders expected texts based on amount of servers', (duplicatedServers, assertions) => {