Merge pull request #791 from acelaya-forks/feature/fix-ref-types

Improved types on element ref objects and their usage
This commit is contained in:
Alejandro Celaya 2023-01-10 20:11:36 +01:00 committed by GitHub
commit aac2832eb7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 26 additions and 31 deletions

View file

@ -1,4 +1,4 @@
import { FC, useEffect, useRef, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { UncontrolledTooltip } from 'reactstrap'; import { UncontrolledTooltip } from 'reactstrap';
import { ExternalLink } from 'react-external-link'; import { ExternalLink } from 'react-external-link';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@ -8,8 +8,8 @@ import {
faCircleNotch as loadingStatusIcon, faCircleNotch as loadingStatusIcon,
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import { MediaMatcher } from '../../utils/types'; import { MediaMatcher } from '../../utils/types';
import { mutableRefToElementRef } from '../../utils/helpers/components';
import { DomainStatus } from '../data'; import { DomainStatus } from '../data';
import { useElementRef } from '../../utils/helpers/hooks';
interface DomainStatusIconProps { interface DomainStatusIconProps {
status: DomainStatus; status: DomainStatus;
@ -17,7 +17,7 @@ interface DomainStatusIconProps {
} }
export const DomainStatusIcon: FC<DomainStatusIconProps> = ({ status, matchMedia = window.matchMedia }) => { export const DomainStatusIcon: FC<DomainStatusIconProps> = ({ status, matchMedia = window.matchMedia }) => {
const ref = useRef<HTMLSpanElement>(); const ref = useElementRef<HTMLSpanElement>();
const matchesMobile = () => matchMedia('(max-width: 991px)').matches; const matchesMobile = () => matchMedia('(max-width: 991px)').matches;
const [isMobile, setIsMobile] = useState<boolean>(matchesMobile()); const [isMobile, setIsMobile] = useState<boolean>(matchesMobile());
@ -35,13 +35,13 @@ export const DomainStatusIcon: FC<DomainStatusIconProps> = ({ status, matchMedia
return ( return (
<> <>
<span ref={mutableRefToElementRef(ref)}> <span ref={ref}>
{status === 'valid' {status === 'valid'
? <FontAwesomeIcon fixedWidth icon={checkIcon} className="text-muted" /> ? <FontAwesomeIcon fixedWidth icon={checkIcon} className="text-muted" />
: <FontAwesomeIcon fixedWidth icon={invalidIcon} className="text-danger" />} : <FontAwesomeIcon fixedWidth icon={invalidIcon} className="text-danger" />}
</span> </span>
<UncontrolledTooltip <UncontrolledTooltip
target={(() => ref.current) as any} target={ref}
placement={isMobile ? 'top-start' : 'left'} placement={isMobile ? 'top-start' : 'left'}
autohide={status === 'valid'} autohide={status === 'valid'}
> >

View file

@ -1,10 +1,9 @@
import { useRef, ChangeEvent, useState, useEffect, FC, PropsWithChildren } from 'react'; import { ChangeEvent, useState, useEffect, FC, PropsWithChildren } from 'react';
import { Button, UncontrolledTooltip } from 'reactstrap'; import { Button, UncontrolledTooltip } from 'reactstrap';
import { complement, pipe } from 'ramda'; import { complement, pipe } from 'ramda';
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 { useToggle } from '../../utils/helpers/hooks'; import { useElementRef, useToggle } from '../../utils/helpers/hooks';
import { mutableRefToElementRef } from '../../utils/helpers/components';
import { ServersImporter } from '../services/ServersImporter'; import { ServersImporter } from '../services/ServersImporter';
import { ServerData, ServersMap } from '../data'; import { ServerData, ServersMap } from '../data';
import { DuplicatedServersModal } from './DuplicatedServersModal'; import { DuplicatedServersModal } from './DuplicatedServersModal';
@ -34,7 +33,7 @@ export const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC
tooltipPlacement = 'bottom', tooltipPlacement = 'bottom',
className = '', className = '',
}) => { }) => {
const ref = useRef<HTMLInputElement>(); const ref = useElementRef<HTMLInputElement>();
const [serversToCreate, setServersToCreate] = useState<ServerData[] | undefined>(); 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();
@ -79,7 +78,7 @@ export const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC
type="file" type="file"
accept="text/csv" accept="text/csv"
className="import-servers-btn__csv-select" className="import-servers-btn__csv-select"
ref={mutableRefToElementRef(ref)} ref={ref}
onChange={onFile} onChange={onFile}
/> />

View file

@ -1,12 +1,12 @@
import { FC, ReactNode, useRef } from 'react'; import { FC, ReactNode } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconDefinition } from '@fortawesome/fontawesome-common-types'; import { IconDefinition } from '@fortawesome/fontawesome-common-types';
import { faLinkSlash, faCalendarXmark, faCheck } from '@fortawesome/free-solid-svg-icons'; import { faLinkSlash, faCalendarXmark, faCheck } from '@fortawesome/free-solid-svg-icons';
import { UncontrolledTooltip } from 'reactstrap'; import { UncontrolledTooltip } from 'reactstrap';
import { isBefore } from 'date-fns'; import { isBefore } from 'date-fns';
import { mutableRefToElementRef } from '../../utils/helpers/components';
import { ShortUrl } from '../data'; import { ShortUrl } from '../data';
import { formatHumanFriendly, now, parseISO } from '../../utils/helpers/date'; import { formatHumanFriendly, now, parseISO } from '../../utils/helpers/date';
import { useElementRef } from '../../utils/helpers/hooks';
interface ShortUrlStatusProps { interface ShortUrlStatusProps {
shortUrl: ShortUrl; shortUrl: ShortUrl;
@ -70,15 +70,15 @@ const resolveShortUrlStatus = (shortUrl: ShortUrl): StatusResult => {
}; };
export const ShortUrlStatus: FC<ShortUrlStatusProps> = ({ shortUrl }) => { export const ShortUrlStatus: FC<ShortUrlStatusProps> = ({ shortUrl }) => {
const tooltipRef = useRef<HTMLElement | undefined>(); const tooltipRef = useElementRef<HTMLElement>();
const { icon, className, description } = resolveShortUrlStatus(shortUrl); const { icon, className, description } = resolveShortUrlStatus(shortUrl);
return ( return (
<> <>
<span style={{ cursor: !description ? undefined : 'help' }} ref={mutableRefToElementRef(tooltipRef)}> <span style={{ cursor: !description ? undefined : 'help' }} ref={tooltipRef}>
<FontAwesomeIcon icon={icon} className={className} /> <FontAwesomeIcon icon={icon} className={className} />
</span> </span>
<UncontrolledTooltip target={(() => tooltipRef.current) as any} placement="bottom"> <UncontrolledTooltip target={tooltipRef} placement="bottom">
{description} {description}
</UncontrolledTooltip> </UncontrolledTooltip>
</> </>

View file

@ -1,4 +1,3 @@
import { useRef } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons'; import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
import { UncontrolledTooltip } from 'reactstrap'; import { UncontrolledTooltip } from 'reactstrap';
@ -7,8 +6,8 @@ import { prettify } from '../../utils/helpers/numbers';
import { ShortUrl } from '../data'; import { ShortUrl } from '../data';
import { SelectedServer } from '../../servers/data'; import { SelectedServer } from '../../servers/data';
import { ShortUrlDetailLink } from './ShortUrlDetailLink'; import { ShortUrlDetailLink } from './ShortUrlDetailLink';
import { mutableRefToElementRef } from '../../utils/helpers/components';
import { formatHumanFriendly, parseISO } from '../../utils/helpers/date'; import { formatHumanFriendly, parseISO } from '../../utils/helpers/date';
import { useElementRef } from '../../utils/helpers/hooks';
import './ShortUrlVisitsCount.scss'; import './ShortUrlVisitsCount.scss';
interface ShortUrlVisitsCountProps { interface ShortUrlVisitsCountProps {
@ -37,20 +36,20 @@ export const ShortUrlVisitsCount = (
return visitsLink; return visitsLink;
} }
const tooltipRef = useRef<HTMLElement | undefined>(); const tooltipRef = useElementRef<HTMLElement>();
return ( return (
<> <>
<span className="indivisible"> <span className="indivisible">
{visitsLink} {visitsLink}
<small className="short-urls-visits-count__max-visits-control" ref={mutableRefToElementRef(tooltipRef)}> <small className="short-urls-visits-count__max-visits-control" ref={tooltipRef}>
{maxVisits && <> / {prettify(maxVisits)}</>} {maxVisits && <> / {prettify(maxVisits)}</>}
<sup className="ms-1"> <sup className="ms-1">
<FontAwesomeIcon icon={infoIcon} /> <FontAwesomeIcon icon={infoIcon} />
</sup> </sup>
</small> </small>
</span> </span>
<UncontrolledTooltip target={(() => tooltipRef.current) as any} placement="bottom"> <UncontrolledTooltip target={tooltipRef} placement="bottom">
<ul className="list-unstyled mb-0"> <ul className="list-unstyled mb-0">
{maxVisits && ( {maxVisits && (
<li className="short-url-visits-count__tooltip-list-item"> <li className="short-url-visits-count__tooltip-list-item">

View file

@ -1,9 +1,9 @@
import { FC, PropsWithChildren, useRef } from 'react'; import { FC, PropsWithChildren } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons'; import { faInfoCircle as infoIcon } from '@fortawesome/free-solid-svg-icons';
import { UncontrolledTooltip } from 'reactstrap'; import { UncontrolledTooltip } from 'reactstrap';
import { Placement } from '@popperjs/core'; import { Placement } from '@popperjs/core';
import { mutableRefToElementRef } from './helpers/components'; import { useElementRef } from './helpers/hooks';
export type InfoTooltipProps = PropsWithChildren<{ export type InfoTooltipProps = PropsWithChildren<{
className?: string; className?: string;
@ -11,14 +11,14 @@ export type InfoTooltipProps = PropsWithChildren<{
}>; }>;
export const InfoTooltip: FC<InfoTooltipProps> = ({ className = '', placement, children }) => { export const InfoTooltip: FC<InfoTooltipProps> = ({ className = '', placement, children }) => {
const ref = useRef<HTMLSpanElement | undefined>(); const ref = useElementRef<HTMLSpanElement>();
return ( return (
<> <>
<span className={className} ref={mutableRefToElementRef(ref)}> <span className={className} ref={ref}>
<FontAwesomeIcon icon={infoIcon} /> <FontAwesomeIcon icon={infoIcon} />
</span> </span>
<UncontrolledTooltip target={(() => ref.current) as any} placement={placement}>{children}</UncontrolledTooltip> <UncontrolledTooltip target={ref} placement={placement}>{children}</UncontrolledTooltip>
</> </>
); );
}; };

View file

@ -1,5 +0,0 @@
import { MutableRefObject, Ref } from 'react';
export const mutableRefToElementRef = <T>(ref: MutableRefObject<T | undefined>): Ref<T> => (el) => {
ref.current = el ?? undefined; // eslint-disable-line no-param-reassign
};

View file

@ -1,4 +1,4 @@
import { useState, useRef, EffectCallback, DependencyList, useEffect } from 'react'; import { DependencyList, EffectCallback, useEffect, useRef, useState } from 'react';
import { useSwipeable as useReactSwipeable } from 'react-swipeable'; import { useSwipeable as useReactSwipeable } from 'react-swipeable';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
@ -91,3 +91,5 @@ export const useDomId = (): string => {
const { current: id } = useRef(`dom-${uuid()}`); const { current: id } = useRef(`dom-${uuid()}`);
return id; return id;
}; };
export const useElementRef = <T>() => useRef<T | null>(null);