diff --git a/.changeset/rotten-kings-rhyme.md b/.changeset/rotten-kings-rhyme.md new file mode 100644 index 00000000..291055e5 --- /dev/null +++ b/.changeset/rotten-kings-rhyme.md @@ -0,0 +1,5 @@ +--- +"@hyperdx/app": patch +--- + +fix: add react-hooks-eslint-plugin and fix issues across app diff --git a/packages/app/eslint.config.mjs b/packages/app/eslint.config.mjs index a56675b5..3d38d3a7 100644 --- a/packages/app/eslint.config.mjs +++ b/packages/app/eslint.config.mjs @@ -50,6 +50,8 @@ export default [ rules: { ...nextPlugin.configs.recommended.rules, ...nextPlugin.configs['core-web-vitals'].rules, + ...reactHooksPlugin.configs.recommended.rules, + 'react-hooks/set-state-in-effect': 'warn', '@typescript-eslint/ban-ts-comment': 'warn', '@typescript-eslint/no-empty-function': 'warn', '@typescript-eslint/no-explicit-any': 'off', diff --git a/packages/app/package.json b/packages/app/package.json index 63806c8a..b4920b53 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -137,6 +137,7 @@ "eslint-config-next": "^16.0.10", "eslint-plugin-playwright": "^2.4.0", "eslint-plugin-react-hook-form": "^0.3.1", + "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-storybook": "10.1.4", "identity-obj-proxy": "^3.0.0", "jest": "^30.2.0", diff --git a/packages/app/src/AutocompleteInput.tsx b/packages/app/src/AutocompleteInput.tsx index 50efdd79..bcd9db3f 100644 --- a/packages/app/src/AutocompleteInput.tsx +++ b/packages/app/src/AutocompleteInput.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; import Fuse from 'fuse.js'; import { Popover, Textarea, UnstyledButton } from '@mantine/core'; @@ -46,9 +46,19 @@ export default function AutocompleteInput({ }) { const suggestionsLimit = 10; - const [isSearchInputFocused, setIsSearchInputFocused] = useState(false); + const [isSearchInputFocused, _setIsSearchInputFocused] = useState(false); const [isInputDropdownOpen, setIsInputDropdownOpen] = useState(false); - const [showSearchHistory, setShowSearchHistory] = useState(false); + const setIsSearchInputFocused = useCallback( + (state: boolean) => { + _setIsSearchInputFocused(state); + setIsInputDropdownOpen(state); + }, + [_setIsSearchInputFocused], + ); + const [rightSectionWidth, setRightSectionWidth] = useState( + 'auto', + ); + const [inputWidth, setInputWidth] = useState(720); const [selectedAutocompleteIndex, setSelectedAutocompleteIndex] = useState(-1); @@ -67,25 +77,11 @@ export default function AutocompleteInput({ }); }, [queryHistory, queryHistoryType]); - useEffect(() => { - if (isSearchInputFocused) { - setIsInputDropdownOpen(true); - } - }, [isSearchInputFocused]); - - useEffect(() => { - // only show search history when: 1.no input, 2.has search type, 3.has history list - if ( - value != null && - value.length === 0 && - queryHistoryList.length > 0 && - queryHistoryType - ) { - setShowSearchHistory(true); - } else { - setShowSearchHistory(false); - } - }, [value, queryHistoryType, queryHistoryList]); + const showSearchHistory = + value != null && + value.length === 0 && + queryHistoryList.length > 0 && + queryHistoryType; const fuse = useMemo( () => @@ -128,6 +124,14 @@ export default function AutocompleteInput({ inputRef.current?.focus(); }; const ref = useRef(null); + useLayoutEffect(() => { + if (ref.current) { + setRightSectionWidth(ref.current.clientWidth); + } + if (inputRef.current) { + setInputWidth(inputRef.current.clientWidth); + } + }, [language, onLanguageChange, inputRef]); return ( 300 - ? inputRef.current?.clientWidth - : 720, + maxWidth: inputWidth > 300 ? inputWidth : 720, width: '100%', zIndex, }, @@ -242,7 +243,7 @@ export default function AutocompleteInput({ } } }} - rightSectionWidth={ref.current?.clientWidth ?? 'auto'} + rightSectionWidth={rightSectionWidth} rightSection={ language != null && onLanguageChange != null ? (
diff --git a/packages/app/src/DBChartPage.tsx b/packages/app/src/DBChartPage.tsx index 496eff2b..4b25a68e 100644 --- a/packages/app/src/DBChartPage.tsx +++ b/packages/app/src/DBChartPage.tsx @@ -164,6 +164,7 @@ function AIAssistant({ {opened && ( + // eslint-disable-next-line react-hooks/refs
- optimizeDefaultOrderBy( - source?.timestampValueExpression ?? '', - source?.displayedTimestampValueExpression, - tableMetadata?.sorting_key, - ), - [source, tableMetadata], - ); + return useMemo(() => { + // If no source, return undefined so that the orderBy is not set incorrectly + if (!source) return undefined; + return optimizeDefaultOrderBy( + source?.timestampValueExpression ?? '', + source?.displayedTimestampValueExpression, + tableMetadata?.sorting_key, + ); + }, [source, tableMetadata]); } // This is outside as it needs to be a stable reference diff --git a/packages/app/src/DOMPlayer.tsx b/packages/app/src/DOMPlayer.tsx index 1d1a4dd4..d90621ae 100644 --- a/packages/app/src/DOMPlayer.tsx +++ b/packages/app/src/DOMPlayer.tsx @@ -264,6 +264,7 @@ export default function DOMPlayer({ ); } + // eslint-disable-next-line react-hooks/immutability updatePlayerTimeRafRef.current = requestAnimationFrame(updatePlayerTime); }, []); diff --git a/packages/app/src/LandingHeader.tsx b/packages/app/src/LandingHeader.tsx index e2af1045..724c86a6 100644 --- a/packages/app/src/LandingHeader.tsx +++ b/packages/app/src/LandingHeader.tsx @@ -12,7 +12,7 @@ export default function LandingHeader({ activeKey: string; fixed?: boolean; }) { - const Wordmark = useWordmark(); + const wordmark = useWordmark(); const { data: me } = api.useMe(); const isLoggedIn = Boolean(me); @@ -36,7 +36,7 @@ export default function LandingHeader({ - + {wordmark} void; } +const NOW = Date.now(); const OnboardingChecklist = ({ onAddDataClick, }: { onAddDataClick?: () => void; }) => { - const Logomark = useLogomark(); const [isCollapsed, setIsCollapsed] = useLocalStorage( 'onboardingChecklistCollapsed', false, @@ -55,7 +54,7 @@ const OnboardingChecklist = ({ // Check if team is new (less than 3 days old) const isNewTeam = useMemo(() => { if (!team?.createdAt) return false; - const threeDaysAgo = new Date(Date.now() - 1000 * 60 * 60 * 24 * 3); + const threeDaysAgo = new Date(NOW - 1000 * 60 * 60 * 24 * 3); return new Date(team.createdAt) > threeDaysAgo; }, [team]); diff --git a/packages/app/src/SessionSubpanel.tsx b/packages/app/src/SessionSubpanel.tsx index 3538c81c..b89e5fd5 100644 --- a/packages/app/src/SessionSubpanel.tsx +++ b/packages/app/src/SessionSubpanel.tsx @@ -17,6 +17,7 @@ import { Button, Divider, Group, + Portal, SegmentedControl, Tooltip, } from '@mantine/core'; @@ -66,16 +67,10 @@ function useSessionChartConfigs({ const { getFieldExpression: getTraceSourceFieldExpression } = useFieldExpressionGenerator(traceSource); - if (!getTraceSourceFieldExpression) { - return { - eventsConfig: undefined, - aliasMap: undefined, - }; - } - // Should produce rows that match the `sessionRowSchema` in packages/app/src/utils/sessions.ts - const select = useMemo( - () => [ + const select = useMemo(() => { + if (!getTraceSourceFieldExpression) return []; + return [ { valueExpression: `${getTraceSourceFieldExpression(traceSource.eventAttributesExpression ?? 'SpanAttributes', 'message')}`, alias: 'body', @@ -145,9 +140,8 @@ function useSessionChartConfigs({ valueExpression: `CAST('span', 'String')`, alias: 'type', }, - ], - [traceSource, getTraceSourceFieldExpression], - ); + ]; + }, [traceSource, getTraceSourceFieldExpression]); // Events shown in the highlighted tab const highlightedEventsFilter = useMemo( @@ -166,8 +160,9 @@ function useSessionChartConfigs({ [traceSource, rumSessionId], ); - const allEventsFilter = useMemo( - () => ({ + const allEventsFilter = useMemo(() => { + if (!getTraceSourceFieldExpression) return undefined; + return { type: 'lucene' as const, condition: `${traceSource.resourceAttributesExpression}.rum.sessionId:"${rumSessionId}" AND ( @@ -179,12 +174,13 @@ function useSessionChartConfigs({ OR ${traceSource.spanNameExpression}:"intercom.onShow" OR ScopeName:"custom-action" )`, - }), - [traceSource, rumSessionId], - ); + }; + }, [traceSource, rumSessionId, getTraceSourceFieldExpression]); - const eventsConfig = useMemo( - () => ({ + const eventsConfig = useMemo(() => { + if (!getTraceSourceFieldExpression || !select || !allEventsFilter) + return undefined; + return { select: select, from: traceSource.from, dateRange: [start, end], @@ -201,21 +197,22 @@ function useSessionChartConfigs({ filters: [ tab === 'highlighted' ? highlightedEventsFilter : allEventsFilter, ], - }), - [ - select, - traceSource, - start, - end, - where, - whereLanguage, - tab, - highlightedEventsFilter, - allEventsFilter, - ], - ); + }; + }, [ + select, + traceSource, + start, + end, + where, + whereLanguage, + tab, + highlightedEventsFilter, + allEventsFilter, + getTraceSourceFieldExpression, + ]); const aliasMap = useMemo(() => { + if (!getTraceSourceFieldExpression) return undefined; // valueExpression: alias return select.reduce( (acc, { valueExpression, alias }) => { @@ -224,7 +221,7 @@ function useSessionChartConfigs({ }, {} as Record, ); - }, [select]); + }, [select, getTraceSourceFieldExpression]); return { eventsConfig, @@ -269,46 +266,23 @@ export default function SessionSubpanel({ const [rowId, setRowId] = useState(undefined); const [aliasWith, setAliasWith] = useState([]); - // Without portaling the nested drawer close overlay will not render properly - const containerRef = useRef(null); - useEffect(() => { - containerRef.current = document.createElement('div'); - - if (containerRef.current) { - document.body.appendChild(containerRef.current); - } - - return () => { - if (containerRef.current) { - document.body.removeChild(containerRef.current); - } - }; - }, []); - - const portaledPanel = - containerRef.current != null - ? ReactDOM.createPortal( - traceSource && ( - { - setDrawerOpen(false); - setRowId(undefined); - }} - /> - ), - containerRef.current, - ) - : null; - const [tsQuery, setTsQuery] = useQueryState( 'ts', parseAsInteger.withOptions({ history: 'replace' }), ); const prevTsQuery = usePrevious(tsQuery); + const [focus, _setFocus] = useState< + { ts: number; setBy: string } | undefined + >( + initialTs != null + ? { + ts: initialTs, + setBy: 'parent', + } + : undefined, + ); + useEffect(() => { if (prevTsQuery == null && tsQuery != null) { _setFocus({ ts: tsQuery, setBy: 'url' }); @@ -327,17 +301,6 @@ export default function SessionSubpanel({ }; }, [setTsQuery]); - const [focus, _setFocus] = useState< - { ts: number; setBy: string } | undefined - >( - initialTs != null - ? { - ts: initialTs, - setBy: 'parent', - } - : undefined, - ); - const setFocus = useCallback( (focus: { ts: number; setBy: string }) => { if (focus.setBy === 'player') { @@ -471,10 +434,44 @@ export default function SessionSubpanel({ }, [setSearchedQuery], ); + const onSessionEventClick = useCallback( + (rowWhere: RowWhereResult) => { + setDrawerOpen(true); + setRowId(rowWhere.where); + setAliasWith(rowWhere.aliasWith); + }, + [setDrawerOpen, setRowId, setAliasWith], + ); + const onSessionEventTimeClick = useCallback( + (ts: number) => { + setFocus({ ts, setBy: 'timeline' }); + }, + [setFocus], + ); + const setPlayerTime = useCallback( + (ts: number) => { + if (focus?.setBy !== 'player' || focus?.ts !== ts) { + setFocus({ ts, setBy: 'player' }); + } + }, + [focus, setFocus], + ); return (
- {rowId != null && portaledPanel} + {rowId != null && traceSource && ( + + { + setDrawerOpen(false); + setRowId(undefined); + }} + /> + + )}
{ - setDrawerOpen(true); - setRowId(rowWhere.where); - setAliasWith(rowWhere.aliasWith); - }, - [setDrawerOpen, setRowId, setAliasWith], - )} + onClick={onSessionEventClick} focus={focus} - onTimeClick={useCallback( - ts => { - setFocus({ ts, setBy: 'timeline' }); - }, - [setFocus], - )} + onTimeClick={onSessionEventTimeClick} minTs={minTs} showRelativeTime={showRelativeTime} /> @@ -563,14 +548,7 @@ export default function SessionSubpanel({ playerState={playerState} setPlayerState={setPlayerState} focus={focus} - setPlayerTime={useCallback( - ts => { - if (focus?.setBy !== 'player' || focus?.ts !== ts) { - setFocus({ ts, setBy: 'player' }); - } - }, - [focus, setFocus], - )} + setPlayerTime={setPlayerTime} config={{ serviceName: session.serviceName, sourceId: sessionSource.id, diff --git a/packages/app/src/Spotlights.tsx b/packages/app/src/Spotlights.tsx index 3ccbb59a..8af6d2b9 100644 --- a/packages/app/src/Spotlights.tsx +++ b/packages/app/src/Spotlights.tsx @@ -22,7 +22,7 @@ import '@mantine/spotlight/styles.css'; export const useSpotlightActions = () => { const router = useRouter(); - const Logomark = useLogomark(); + const logomark = useLogomark({ size: 16 }); const { data: logViewsData } = useSavedSearches(); const { data: dashboardsData } = api.useDashboards(); @@ -151,7 +151,7 @@ export const useSpotlightActions = () => { { id: 'cloud', group: 'Menu', - leftSection: , + leftSection: logomark, label: 'HyperDX Cloud', description: 'Ready to use HyperDX Cloud? Get started for free.', keywords: ['account', 'profile'], @@ -162,7 +162,7 @@ export const useSpotlightActions = () => { ); return logViewActions; - }, [Logomark, logViewsData, dashboardsData, router]); + }, [logomark, logViewsData, dashboardsData, router]); return { actions }; }; diff --git a/packages/app/src/components/AppNav/AppNav.tsx b/packages/app/src/components/AppNav/AppNav.tsx index c5d2e225..279abb24 100644 --- a/packages/app/src/components/AppNav/AppNav.tsx +++ b/packages/app/src/components/AppNav/AppNav.tsx @@ -391,8 +391,8 @@ function useSearchableList({ } export default function AppNav({ fixed = false }: { fixed?: boolean }) { - const Wordmark = useWordmark(); - const Logomark = useLogomark(); + const wordmark = useWordmark(); + const logomark = useLogomark({ size: 22 }); useEffect(() => { let redirectUrl; @@ -676,12 +676,10 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) { > {isCollapsed ? ( -
- -
+
{logomark}
) : ( - + {wordmark} {isUTC && ( { - if (key.startsWith('__hdx_')) { - delete data[key]; - } - }); + const cleanedData = Object.fromEntries( + Object.entries(data).filter(entry => !entry[0].startsWith('__hdx_')), + ); - return filterObjectRecursively(data, debouncedFilter); + return filterObjectRecursively(cleanedData, debouncedFilter); }, [data, debouncedFilter]); const getLineActions = useCallback( diff --git a/packages/app/src/components/DBRowTable.tsx b/packages/app/src/components/DBRowTable.tsx index 0db27145..625bd8f5 100644 --- a/packages/app/src/components/DBRowTable.tsx +++ b/packages/app/src/components/DBRowTable.tsx @@ -423,7 +423,19 @@ export const RawLogTable = memo( const FETCH_NEXT_PAGE_PX = 500; //we need a reference to the scrolling element for logic down below - const tableContainerRef = useRef(null); + const [tableContainerRef, setTableContainerRef] = + useState(null); + const tableContainerRefCallback = useCallback( + (node: HTMLDivElement): (() => void) => { + if (node) { + setTableContainerRef(node); + } + return () => { + setTableContainerRef(null); + }; + }, + [], + ); // Get the alias map from the config so we resolve correct column ids const { data: aliasMap } = useAliasMapFromChartConfig(config); @@ -431,10 +443,10 @@ export const RawLogTable = memo( // Reset scroll when live tail is enabled for the first time const prevIsLive = usePrevious(isLive); useEffect(() => { - if (isLive && prevIsLive === false && tableContainerRef.current != null) { - tableContainerRef.current.scrollTop = 0; + if (isLive && prevIsLive === false && tableContainerRef != null) { + tableContainerRef.scrollTop = 0; } - }, [isLive, prevIsLive]); + }, [isLive, prevIsLive, tableContainerRef]); const logLevelColumn = useMemo(() => { return inferLogLevelColumn(dedupedRows); @@ -600,8 +612,8 @@ export const RawLogTable = memo( //a check on mount and after a fetch to see if the table is already scrolled to the bottom and immediately needs to fetch more data useEffect(() => { - fetchMoreOnBottomReached(tableContainerRef.current); - }, [fetchMoreOnBottomReached]); + fetchMoreOnBottomReached(tableContainerRef); + }, [fetchMoreOnBottomReached, tableContainerRef]); const reactTableProps = useMemo((): TableOptions => { //TODO: fix any @@ -664,7 +676,7 @@ export const RawLogTable = memo( count: _rows.length, // count: hasNextPage ? allRows.length + 1 : allRows.length, getScrollElement: useCallback( - () => tableContainerRef.current, + () => tableContainerRef, [tableContainerRef], ), estimateSize: useCallback(() => 23, []), @@ -853,7 +865,7 @@ export const RawLogTable = memo( onScroll?.(scrollTop); } }} - ref={tableContainerRef} + ref={tableContainerRefCallback} // Fixes flickering scroll bar: https://github.com/TanStack/virtual/issues/426#issuecomment-1403438040 // style={{ overflowAnchor: 'none' }} > diff --git a/packages/app/src/components/DBSessionPanel.tsx b/packages/app/src/components/DBSessionPanel.tsx index 49485fca..18d770c0 100644 --- a/packages/app/src/components/DBSessionPanel.tsx +++ b/packages/app/src/components/DBSessionPanel.tsx @@ -64,22 +64,19 @@ export const useSessionId = ({ }); const result = useMemo(() => { - for (const row of data?.data || []) { - if (row.parentSpanId === null && row.rumSessionId) { - return { - rumServiceName: row.serviceName, - rumSessionId: row.rumSessionId, - }; - } + const rowData = data?.data || []; + let row = rowData.find( + row => row.parentSpanId === null && row.rumSessionId, + ); + if (!row) { + // otherwise just return the first session id + row = rowData.find(row => row.rumSessionId); } - // otherwise just return the first session id - for (const row of data?.data || []) { - if (row.rumSessionId) { - return { - rumServiceName: row.serviceName, - rumSessionId: row.rumSessionId, - }; - } + if (row) { + return { + rumServiceName: row.serviceName, + rumSessionId: row.rumSessionId, + }; } return { rumServiceName: undefined, rumSessionId: undefined }; }, [data]); diff --git a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx index ec3e1d09..6114df5f 100644 --- a/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx +++ b/packages/app/src/components/DBTable/DBRowTableFieldWithPopover.tsx @@ -15,7 +15,7 @@ export interface DBRowTableFieldWithPopoverProps { cellValue: unknown; wrapLinesEnabled: boolean; columnName?: string; - tableContainerRef?: React.RefObject; + tableContainerRef: HTMLDivElement | null; isChart?: boolean; } @@ -148,7 +148,7 @@ export const DBRowTableFieldWithPopover = ({ position="top-start" offset={5} opened={opened} - portalProps={{ target: tableContainerRef?.current ?? undefined }} + portalProps={{ target: tableContainerRef ?? undefined }} closeOnClickOutside={false} clickOutsideEvents={[]} > diff --git a/packages/app/src/components/DBTable/TableSearchInput.tsx b/packages/app/src/components/DBTable/TableSearchInput.tsx index bc252d6d..ab31b587 100644 --- a/packages/app/src/components/DBTable/TableSearchInput.tsx +++ b/packages/app/src/components/DBTable/TableSearchInput.tsx @@ -93,7 +93,7 @@ interface TableSearchInputProps { /** * Reference to a container element to check if it's clickable (not obscured by modal/drawer) */ - containerRef?: React.RefObject; + containerRef?: HTMLElement | null; } /** @@ -145,8 +145,7 @@ export const TableSearchInput = ({ // Detect Cmd+F (Mac) or Ctrl+F (Windows/Linux) if ((e.metaKey || e.ctrlKey) && e.key === 'f') { // if container exists, verify it's actually clickable - if (containerRef?.current && !isElementClickable(containerRef.current)) - return; + if (containerRef && !isElementClickable(containerRef)) return; e.preventDefault(); handleShow(); diff --git a/packages/app/src/components/DBTableChart.tsx b/packages/app/src/components/DBTableChart.tsx index 9a5ad2da..d56f540e 100644 --- a/packages/app/src/components/DBTableChart.tsx +++ b/packages/app/src/components/DBTableChart.tsx @@ -103,7 +103,7 @@ export default function DBTableChart({ } return acc; }, [] as string[]); - }, [config?.select]); + }, [config.select]); const columns = useMemo(() => { const rows = data?.data ?? []; if (rows.length === 0) { diff --git a/packages/app/src/components/NumberFormat.tsx b/packages/app/src/components/NumberFormat.tsx index 26b44efe..dbb11ab3 100644 --- a/packages/app/src/components/NumberFormat.tsx +++ b/packages/app/src/components/NumberFormat.tsx @@ -153,15 +153,12 @@ export const NumberFormatForm: React.FC<{ key="numberFormat.factor" name="numberFormat.factor" render={({ field: { value, onChange, ...field } }) => { - const options = useMemo( - () => [ - { value: '1', label: 'Seconds' }, - { value: '0.001', label: 'Milliseconds' }, - { value: '0.000001', label: 'Microseconds' }, - { value: '0.000000001', label: 'Nanoseconds' }, - ], - [], - ); + const options = [ + { value: '1', label: 'Seconds' }, + { value: '0.001', label: 'Milliseconds' }, + { value: '0.000001', label: 'Microseconds' }, + { value: '0.000000001', label: 'Nanoseconds' }, + ]; const stringValue = options.find(option => parseFloat(option.value) === value) diff --git a/packages/app/src/components/SQLEditor.tsx b/packages/app/src/components/SQLEditor.tsx index 7f3463e9..54e9a616 100644 --- a/packages/app/src/components/SQLEditor.tsx +++ b/packages/app/src/components/SQLEditor.tsx @@ -60,6 +60,7 @@ export default function SQLEditor({ minHeight={'100px'} extensions={[ styleTheme, + // eslint-disable-next-line react-hooks/refs compartmentRef.current.of( sql({ upperCaseKeywords: true, diff --git a/packages/app/src/components/SQLInlineEditor.tsx b/packages/app/src/components/SQLInlineEditor.tsx index f88f437b..74f40544 100644 --- a/packages/app/src/components/SQLInlineEditor.tsx +++ b/packages/app/src/components/SQLInlineEditor.tsx @@ -369,6 +369,7 @@ export default function SQLInlineEditor({ ...tooltipExt, createStyleTheme(), ...(allowMultiline ? [EditorView.lineWrapping] : []), + // eslint-disable-next-line react-hooks/refs compartmentRef.current.of( sql({ upperCaseKeywords: true, diff --git a/packages/app/src/components/SelectControlled.tsx b/packages/app/src/components/SelectControlled.tsx index 1cd6909e..866a5f51 100644 --- a/packages/app/src/components/SelectControlled.tsx +++ b/packages/app/src/components/SelectControlled.tsx @@ -9,7 +9,16 @@ export type SelectControlledProps = SelectProps & }; export default function SelectControlled(props: SelectControlledProps) { - const { field, fieldState } = useController(props); + const { + field: { + value: fieldValue, + onChange: fieldOnChange, + onBlur: fieldOnBlur, + name: fieldName, + ref: fieldRef, + }, + fieldState, + } = useController(props); const { onCreate, allowDeselect = true, ...restProps } = props; // This is needed as mantine does not clear the select @@ -17,9 +26,9 @@ export default function SelectControlled(props: SelectControlledProps) { // if it was previously in the data (ex. data was deleted) const selected = props.data?.find(d => typeof d === 'string' - ? d === field.value + ? d === fieldValue : 'value' in d - ? d.value === field.value + ? d.value === fieldValue : true, ); @@ -28,21 +37,21 @@ export default function SelectControlled(props: SelectControlledProps) { if (value === '_create_new_value' && onCreate != null) { onCreate(); } else if (value !== null || allowDeselect) { - field.onChange(value); + fieldOnChange(value); } }, - [field, onCreate, allowDeselect], + [fieldOnChange, onCreate, allowDeselect], ); return (