mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-10 10:33:11 +03:00
upgrade to React 19, react-konva v19, eslint-plugin-react-hooks v5
Core React 19 upgrade with all necessary type fixes: - Update RefObject types to accept T | null (React 19 refs always nullable) - Add JSX namespace imports (no longer global in React 19) - Add initial values to useRef calls (required in React 19) - Fix ReactElement.props unknown type in config-form components - Fix IconWrapper interface to use HTMLAttributes instead of index signature - Add monaco-editor as dev dependency for type declarations - Upgrade react-konva to v19, eslint-plugin-react-hooks to v5
This commit is contained in:
parent
cf7535338a
commit
7626bc0344
4035
web/package-lock.json
generated
4035
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -63,17 +63,17 @@
|
||||
"monaco-yaml": "^5.3.1",
|
||||
"next-themes": "^0.4.6",
|
||||
"nosleep.js": "^0.12.0",
|
||||
"react": "^18.3.1",
|
||||
"react": "^19.2.4",
|
||||
"react-apexcharts": "^1.4.1",
|
||||
"react-day-picker": "^9.7.0",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-grid-layout": "^2.2.2",
|
||||
"react-hook-form": "^7.52.1",
|
||||
"react-i18next": "^15.2.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-konva": "^19.2.3",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-router-dom": "^6.30.3",
|
||||
"react-swipeable": "^7.0.2",
|
||||
@ -99,9 +99,10 @@
|
||||
"@tailwindcss/forms": "^0.5.9",
|
||||
"@testing-library/jest-dom": "^6.6.2",
|
||||
"@types/lodash": "^4.17.12",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"@types/node": "^20.14.10",
|
||||
"@types/react": "^18.3.2",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react-icons": "^3.0.0",
|
||||
"@types/react-transition-group": "^4.4.10",
|
||||
"@types/strftime": "^0.9.8",
|
||||
@ -114,7 +115,7 @@
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-jest": "^28.2.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.8",
|
||||
"eslint-plugin-vitest-globals": "^1.5.0",
|
||||
"fake-indexeddb": "^6.0.0",
|
||||
|
||||
@ -114,10 +114,17 @@ interface PropertyElement {
|
||||
content: React.ReactElement;
|
||||
}
|
||||
|
||||
/** Shape of the props that RJSF injects into each property element. */
|
||||
interface RjsfElementProps {
|
||||
schema?: { type?: string | string[] };
|
||||
uiSchema?: Record<string, unknown> & {
|
||||
"ui:widget"?: string;
|
||||
"ui:options"?: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
function isObjectLikeElement(item: PropertyElement) {
|
||||
const fieldSchema = item.content.props?.schema as
|
||||
| { type?: string | string[] }
|
||||
| undefined;
|
||||
const fieldSchema = (item.content.props as RjsfElementProps)?.schema;
|
||||
return fieldSchema?.type === "object";
|
||||
}
|
||||
|
||||
@ -163,16 +170,21 @@ function GridLayoutObjectFieldTemplate(
|
||||
|
||||
// Override the properties rendering with grid layout
|
||||
const isHiddenProp = (prop: (typeof properties)[number]) =>
|
||||
prop.content.props.uiSchema?.["ui:widget"] === "hidden";
|
||||
(prop.content.props as RjsfElementProps).uiSchema?.["ui:widget"] ===
|
||||
"hidden";
|
||||
|
||||
const visibleProps = properties.filter((prop) => !isHiddenProp(prop));
|
||||
|
||||
// Separate regular and advanced properties
|
||||
const advancedProps = visibleProps.filter(
|
||||
(p) => p.content.props.uiSchema?.["ui:options"]?.advanced === true,
|
||||
(p) =>
|
||||
(p.content.props as RjsfElementProps).uiSchema?.["ui:options"]
|
||||
?.advanced === true,
|
||||
);
|
||||
const regularProps = visibleProps.filter(
|
||||
(p) => p.content.props.uiSchema?.["ui:options"]?.advanced !== true,
|
||||
(p) =>
|
||||
(p.content.props as RjsfElementProps).uiSchema?.["ui:options"]
|
||||
?.advanced !== true,
|
||||
);
|
||||
const hasModifiedAdvanced = advancedProps.some((prop) =>
|
||||
isPathModified([...fieldPath, prop.name]),
|
||||
|
||||
@ -448,6 +448,12 @@ export function FieldTemplate(props: FieldTemplateProps) {
|
||||
);
|
||||
};
|
||||
|
||||
const errorsProps = errors?.props as
|
||||
| { errors?: unknown[] }
|
||||
| undefined;
|
||||
const hasFieldErrors =
|
||||
!!errors && (errorsProps?.errors?.length ?? 0) > 0;
|
||||
|
||||
const renderStandardLabel = () => {
|
||||
if (!shouldRenderStandardLabel) {
|
||||
return null;
|
||||
@ -459,7 +465,7 @@ export function FieldTemplate(props: FieldTemplateProps) {
|
||||
className={cn(
|
||||
"text-sm font-medium",
|
||||
isModified && "text-danger",
|
||||
errors && errors.props?.errors?.length > 0 && "text-destructive",
|
||||
hasFieldErrors && "text-destructive",
|
||||
)}
|
||||
>
|
||||
{finalLabel}
|
||||
@ -497,7 +503,7 @@ export function FieldTemplate(props: FieldTemplateProps) {
|
||||
className={cn(
|
||||
"text-sm font-medium",
|
||||
isModified && "text-danger",
|
||||
errors && errors.props?.errors?.length > 0 && "text-destructive",
|
||||
hasFieldErrors && "text-destructive",
|
||||
)}
|
||||
>
|
||||
{finalLabel}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Custom MultiSchemaFieldTemplate to handle anyOf [Type, null] fields
|
||||
// Renders simple nullable types as single inputs instead of dropdowns
|
||||
|
||||
import type { JSX } from "react";
|
||||
import {
|
||||
MultiSchemaFieldTemplateProps,
|
||||
StrictRJSFSchema,
|
||||
|
||||
@ -25,6 +25,15 @@ import {
|
||||
import get from "lodash/get";
|
||||
import { AddPropertyButton, AdvancedCollapsible } from "../components";
|
||||
|
||||
/** Shape of the props that RJSF injects into each property element. */
|
||||
interface RjsfElementProps {
|
||||
schema?: { type?: string | string[] };
|
||||
uiSchema?: Record<string, unknown> & {
|
||||
"ui:widget"?: string;
|
||||
"ui:options"?: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
|
||||
export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
||||
const {
|
||||
title,
|
||||
@ -182,16 +191,21 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
||||
uiSchema?.["ui:options"]?.disableNestedCard === true;
|
||||
|
||||
const isHiddenProp = (prop: (typeof properties)[number]) =>
|
||||
prop.content.props.uiSchema?.["ui:widget"] === "hidden";
|
||||
(prop.content.props as RjsfElementProps).uiSchema?.["ui:widget"] ===
|
||||
"hidden";
|
||||
|
||||
const visibleProps = properties.filter((prop) => !isHiddenProp(prop));
|
||||
|
||||
// Check for advanced section grouping
|
||||
const advancedProps = visibleProps.filter(
|
||||
(p) => p.content.props.uiSchema?.["ui:options"]?.advanced === true,
|
||||
(p) =>
|
||||
(p.content.props as RjsfElementProps).uiSchema?.["ui:options"]
|
||||
?.advanced === true,
|
||||
);
|
||||
const regularProps = visibleProps.filter(
|
||||
(p) => p.content.props.uiSchema?.["ui:options"]?.advanced !== true,
|
||||
(p) =>
|
||||
(p.content.props as RjsfElementProps).uiSchema?.["ui:options"]
|
||||
?.advanced !== true,
|
||||
);
|
||||
const hasModifiedAdvanced = advancedProps.some((prop) =>
|
||||
checkSubtreeModified([...fieldPath, prop.name]),
|
||||
@ -333,9 +347,7 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
||||
|
||||
const ungrouped = items.filter((item) => !grouped.has(item.name));
|
||||
const isObjectLikeField = (item: (typeof properties)[number]) => {
|
||||
const fieldSchema = item.content.props.schema as
|
||||
| { type?: string | string[] }
|
||||
| undefined;
|
||||
const fieldSchema = (item.content.props as RjsfElementProps)?.schema;
|
||||
return fieldSchema?.type === "object";
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { t } from "i18next";
|
||||
import type { JSX } from "react";
|
||||
import { FunctionComponent, useEffect, useMemo, useState } from "react";
|
||||
|
||||
interface IProp {
|
||||
|
||||
@ -102,7 +102,7 @@ export function MobilePagePortal({
|
||||
type MobilePageContentProps = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
scrollerRef?: React.RefObject<HTMLDivElement>;
|
||||
scrollerRef?: React.RefObject<HTMLDivElement | null>;
|
||||
};
|
||||
|
||||
export function MobilePageContent({
|
||||
|
||||
@ -10,7 +10,7 @@ import Konva from "konva";
|
||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||
|
||||
type DebugDrawingLayerProps = {
|
||||
containerRef: React.RefObject<HTMLDivElement>;
|
||||
containerRef: React.RefObject<HTMLDivElement | null>;
|
||||
cameraWidth: number;
|
||||
cameraHeight: number;
|
||||
};
|
||||
|
||||
@ -17,7 +17,7 @@ type ObjectPathProps = {
|
||||
color?: number[];
|
||||
width?: number;
|
||||
pointRadius?: number;
|
||||
imgRef: React.RefObject<HTMLImageElement>;
|
||||
imgRef: React.RefObject<HTMLImageElement | null>;
|
||||
onPointClick?: (index: number) => void;
|
||||
visible?: boolean;
|
||||
};
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
} from "@/components/ui/sheet";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import type { JSX } from "react";
|
||||
import { useRef } from "react";
|
||||
|
||||
type PlatformAwareDialogProps = {
|
||||
|
||||
@ -91,7 +91,7 @@ export default function HlsVideoPlayer({
|
||||
|
||||
// playback
|
||||
|
||||
const hlsRef = useRef<Hls>();
|
||||
const hlsRef = useRef<Hls>(undefined);
|
||||
const [useHlsCompat, setUseHlsCompat] = useState(false);
|
||||
const [loadedMetadata, setLoadedMetadata] = useState(false);
|
||||
const [bufferTimeout, setBufferTimeout] = useState<NodeJS.Timeout>();
|
||||
|
||||
@ -51,10 +51,10 @@ export default function WebRtcPlayer({
|
||||
|
||||
// camera states
|
||||
|
||||
const pcRef = useRef<RTCPeerConnection | undefined>();
|
||||
const pcRef = useRef<RTCPeerConnection | undefined>(undefined);
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
const [bufferTimeout, setBufferTimeout] = useState<NodeJS.Timeout>();
|
||||
const videoLoadTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
const videoLoadTimeoutRef = useRef<NodeJS.Timeout>(undefined);
|
||||
|
||||
const PeerConnection = useCallback(
|
||||
async (media: string) => {
|
||||
|
||||
@ -10,7 +10,7 @@ import { snapPointToLines } from "@/utils/canvasUtil";
|
||||
import { usePolygonStates } from "@/hooks/use-polygon-states";
|
||||
|
||||
type PolygonCanvasProps = {
|
||||
containerRef: RefObject<HTMLDivElement>;
|
||||
containerRef: RefObject<HTMLDivElement | null>;
|
||||
camera: string;
|
||||
width: number;
|
||||
height: number;
|
||||
|
||||
@ -18,7 +18,7 @@ import Konva from "konva";
|
||||
import { Vector2d } from "konva/lib/types";
|
||||
|
||||
type PolygonDrawerProps = {
|
||||
stageRef: RefObject<Konva.Stage>;
|
||||
stageRef: RefObject<Konva.Stage | null>;
|
||||
points: number[][];
|
||||
distances: number[];
|
||||
isActive: boolean;
|
||||
|
||||
@ -37,8 +37,8 @@ export type EventReviewTimelineProps = {
|
||||
events: ReviewSegment[];
|
||||
visibleTimestamps?: number[];
|
||||
severityType: ReviewSeverity;
|
||||
timelineRef?: RefObject<HTMLDivElement>;
|
||||
contentRef: RefObject<HTMLDivElement>;
|
||||
timelineRef?: RefObject<HTMLDivElement | null>;
|
||||
contentRef: RefObject<HTMLDivElement | null>;
|
||||
onHandlebarDraggingChange?: (isDragging: boolean) => void;
|
||||
isZooming: boolean;
|
||||
zoomDirection: TimelineZoomDirection;
|
||||
|
||||
@ -28,7 +28,7 @@ type EventSegmentProps = {
|
||||
minimapStartTime?: number;
|
||||
minimapEndTime?: number;
|
||||
severityType: ReviewSeverity;
|
||||
contentRef: RefObject<HTMLDivElement>;
|
||||
contentRef: RefObject<HTMLDivElement | null>;
|
||||
setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||
scrollToSegment: (segmentTime: number, ifNeeded?: boolean) => void;
|
||||
dense: boolean;
|
||||
|
||||
@ -41,8 +41,8 @@ export type MotionReviewTimelineProps = {
|
||||
events: ReviewSegment[];
|
||||
motion_events: MotionData[];
|
||||
noRecordingRanges?: RecordingSegment[];
|
||||
contentRef: RefObject<HTMLDivElement>;
|
||||
timelineRef?: RefObject<HTMLDivElement>;
|
||||
contentRef: RefObject<HTMLDivElement | null>;
|
||||
timelineRef?: RefObject<HTMLDivElement | null>;
|
||||
onHandlebarDraggingChange?: (isDragging: boolean) => void;
|
||||
dense?: boolean;
|
||||
isZooming: boolean;
|
||||
|
||||
@ -20,8 +20,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
||||
import { TooltipPortal } from "@radix-ui/react-tooltip";
|
||||
|
||||
export type ReviewTimelineProps = {
|
||||
timelineRef: RefObject<HTMLDivElement>;
|
||||
contentRef: RefObject<HTMLDivElement>;
|
||||
timelineRef: RefObject<HTMLDivElement | null>;
|
||||
contentRef: RefObject<HTMLDivElement | null>;
|
||||
segmentDuration: number;
|
||||
timelineDuration: number;
|
||||
timelineStartAligned: number;
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
|
||||
|
||||
export type SummaryTimelineProps = {
|
||||
reviewTimelineRef: React.RefObject<HTMLDivElement>;
|
||||
reviewTimelineRef: React.RefObject<HTMLDivElement | null>;
|
||||
timelineStart: number;
|
||||
timelineEnd: number;
|
||||
segmentDuration: number;
|
||||
|
||||
@ -10,7 +10,7 @@ import { EventSegment } from "./EventSegment";
|
||||
import { ReviewSegment, ReviewSeverity } from "@/types/review";
|
||||
|
||||
type VirtualizedEventSegmentsProps = {
|
||||
timelineRef: React.RefObject<HTMLDivElement>;
|
||||
timelineRef: React.RefObject<HTMLDivElement | null>;
|
||||
segments: number[];
|
||||
events: ReviewSegment[];
|
||||
segmentDuration: number;
|
||||
@ -19,7 +19,7 @@ type VirtualizedEventSegmentsProps = {
|
||||
minimapStartTime?: number;
|
||||
minimapEndTime?: number;
|
||||
severityType: ReviewSeverity;
|
||||
contentRef: React.RefObject<HTMLDivElement>;
|
||||
contentRef: React.RefObject<HTMLDivElement | null>;
|
||||
setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||
dense: boolean;
|
||||
alignStartDateToTimeline: (timestamp: number) => number;
|
||||
|
||||
@ -10,7 +10,7 @@ import MotionSegment from "./MotionSegment";
|
||||
import { ReviewSegment, MotionData } from "@/types/review";
|
||||
|
||||
type VirtualizedMotionSegmentsProps = {
|
||||
timelineRef: React.RefObject<HTMLDivElement>;
|
||||
timelineRef: React.RefObject<HTMLDivElement | null>;
|
||||
segments: number[];
|
||||
events: ReviewSegment[];
|
||||
motion_events: MotionData[];
|
||||
@ -19,7 +19,7 @@ type VirtualizedMotionSegmentsProps = {
|
||||
showMinimap: boolean;
|
||||
minimapStartTime?: number;
|
||||
minimapEndTime?: number;
|
||||
contentRef: React.RefObject<HTMLDivElement>;
|
||||
contentRef: React.RefObject<HTMLDivElement | null>;
|
||||
setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||
dense: boolean;
|
||||
motionOnly: boolean;
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { JSX } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { Button } from "./button";
|
||||
import { Calendar } from "./calendar";
|
||||
@ -124,8 +125,8 @@ export function DateRangePicker({
|
||||
);
|
||||
|
||||
// Refs to store the values of range and rangeCompare when the date picker is opened
|
||||
const openedRangeRef = useRef<DateRange | undefined>();
|
||||
const openedRangeCompareRef = useRef<DateRange | undefined>();
|
||||
const openedRangeRef = useRef<DateRange | undefined>(undefined);
|
||||
const openedRangeCompareRef = useRef<DateRange | undefined>(undefined);
|
||||
|
||||
const [selectedPreset, setSelectedPreset] = useState<string | undefined>(
|
||||
undefined,
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { ForwardedRef, forwardRef } from "react";
|
||||
import { IconType } from "react-icons";
|
||||
|
||||
interface IconWrapperProps {
|
||||
interface IconWrapperProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
icon: IconType;
|
||||
className?: string;
|
||||
[key: string]: any;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const IconWrapper = forwardRef(
|
||||
|
||||
@ -8,10 +8,10 @@ import { useTranslation } from "react-i18next";
|
||||
import useUserInteraction from "./use-user-interaction";
|
||||
|
||||
type DraggableElementProps = {
|
||||
contentRef: React.RefObject<HTMLElement>;
|
||||
timelineRef: React.RefObject<HTMLDivElement>;
|
||||
segmentsRef: React.RefObject<HTMLDivElement>;
|
||||
draggableElementRef: React.RefObject<HTMLDivElement>;
|
||||
contentRef: React.RefObject<HTMLElement | null>;
|
||||
timelineRef: React.RefObject<HTMLDivElement | null>;
|
||||
segmentsRef: React.RefObject<HTMLDivElement | null>;
|
||||
draggableElementRef: React.RefObject<HTMLDivElement | null>;
|
||||
segmentDuration: number;
|
||||
showDraggableElement: boolean;
|
||||
draggableElementTime?: number;
|
||||
|
||||
@ -78,7 +78,7 @@ function removeEventListeners(
|
||||
}
|
||||
|
||||
export function useFullscreen<T extends HTMLElement = HTMLElement>(
|
||||
elementRef: RefObject<T>,
|
||||
elementRef: RefObject<T | null>,
|
||||
) {
|
||||
const [fullscreen, setFullscreen] = useState<boolean>(false);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
const useImageLoaded = (): [
|
||||
React.RefObject<HTMLImageElement>,
|
||||
React.RefObject<HTMLImageElement | null>,
|
||||
boolean,
|
||||
() => void,
|
||||
] => {
|
||||
|
||||
@ -3,7 +3,7 @@ import { useCallback } from "react";
|
||||
export type TimelineUtilsProps = {
|
||||
segmentDuration: number;
|
||||
timelineDuration?: number;
|
||||
timelineRef?: React.RefObject<HTMLElement>;
|
||||
timelineRef?: React.RefObject<HTMLElement | null>;
|
||||
};
|
||||
|
||||
export function useTimelineUtils({
|
||||
|
||||
@ -11,7 +11,7 @@ type UseTimelineZoomProps = {
|
||||
zoomLevels: ZoomSettings[];
|
||||
onZoomChange: (newZoomLevel: number) => void;
|
||||
pinchThresholdPercent?: number;
|
||||
timelineRef: React.RefObject<HTMLDivElement>;
|
||||
timelineRef: React.RefObject<HTMLDivElement | null>;
|
||||
timelineDuration: number;
|
||||
};
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
type UseUserInteractionProps = {
|
||||
elementRef: React.RefObject<HTMLElement>;
|
||||
elementRef: React.RefObject<HTMLElement | null>;
|
||||
};
|
||||
|
||||
function useUserInteraction({ elementRef }: UseUserInteractionProps) {
|
||||
const [userInteracting, setUserInteracting] = useState(false);
|
||||
const interactionTimeout = useRef<NodeJS.Timeout>();
|
||||
const interactionTimeout = useRef<NodeJS.Timeout>(undefined);
|
||||
const isProgrammaticScroll = useRef(false);
|
||||
|
||||
const setProgrammaticScroll = useCallback(() => {
|
||||
|
||||
@ -7,7 +7,7 @@ export type VideoResolutionType = {
|
||||
};
|
||||
|
||||
export function useVideoDimensions(
|
||||
containerRef: React.RefObject<HTMLDivElement>,
|
||||
containerRef: React.RefObject<HTMLDivElement | null>,
|
||||
) {
|
||||
const [{ width: containerWidth, height: containerHeight }] =
|
||||
useResizeObserver(containerRef);
|
||||
|
||||
@ -56,7 +56,7 @@ type DraggableGridLayoutProps = {
|
||||
cameras: CameraConfig[];
|
||||
cameraGroup: string;
|
||||
cameraRef: (node: HTMLElement | null) => void;
|
||||
containerRef: React.RefObject<HTMLDivElement>;
|
||||
containerRef: React.RefObject<HTMLDivElement | null>;
|
||||
includeBirdseye: boolean;
|
||||
onSelectCamera: (camera: string) => void;
|
||||
windowVisible: boolean;
|
||||
|
||||
@ -419,7 +419,7 @@ export default function SearchView({
|
||||
>();
|
||||
|
||||
// keep track of previous ref to outline thumbnail when dialog closes
|
||||
const prevSearchDetailRef = useRef<SearchResult | undefined>();
|
||||
const prevSearchDetailRef = useRef<SearchResult | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchDetail === undefined && prevSearchDetailRef.current) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user