diff --git a/components/src/OptionsEditorLayout/OptionsEditorControl.tsx b/components/src/OptionsEditorLayout/OptionsEditorControl.tsx index ce772b47..22830831 100644 --- a/components/src/OptionsEditorLayout/OptionsEditorControl.tsx +++ b/components/src/OptionsEditorLayout/OptionsEditorControl.tsx @@ -15,10 +15,9 @@ import type { FormControlLabelProps } from '@mui/material'; import { FormControl, FormLabel, Stack, Box, IconButton } from '@mui/material'; import InformationOutlineIcon from 'mdi-material-ui/InformationOutline'; import type { ReactElement } from 'react'; -import React from 'react'; +import React, { useId } from 'react'; import { InfoTooltip } from '../InfoTooltip'; -import { useId } from '../utils'; export type OptionsEditorControlProps = Pick & { description?: string; @@ -27,7 +26,7 @@ export type OptionsEditorControlProps = Pick { // Make sure we have a unique ID we can use for associating labels and // controls for a11y. - const generatedControlId = useId('EditorSectionControl'); + const generatedControlId = `EditorSectionControl-${useId()}`; const controlId = `${generatedControlId}-control`; const labelId = `${generatedControlId}-label`; diff --git a/components/src/utils/component-ids.test.ts b/components/src/utils/component-ids.test.ts deleted file mode 100644 index 8bab32d9..00000000 --- a/components/src/utils/component-ids.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright The Perses Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { renderHook } from '@testing-library/react'; - -import { useId } from './component-ids'; - -it('generates unique IDs that keep their original prefix across rerenders', () => { - const { result, rerender } = renderHook(({ prefix }) => [useId(prefix), useId(prefix)], { - initialProps: { prefix: 'control' }, - }); - const initialIds = result.current; - expect(initialIds[0]).toMatch(/^control-/); - expect(initialIds[0]).not.toBe(initialIds[1]); - rerender({ prefix: 'changed' }); - expect(result.current).toEqual(initialIds); -}); diff --git a/components/src/utils/component-ids.ts b/components/src/utils/component-ids.ts deleted file mode 100644 index 0d3d0d3e..00000000 --- a/components/src/utils/component-ids.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright The Perses Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { useId as useReactId, useState } from 'react'; - -/** Generates a unique, stable React ID with the given prefix. */ -export function useId(prefix: string): string { - const id = useReactId(); - const [initialPrefix] = useState(prefix); - return `${initialPrefix}-${id}`; -} diff --git a/components/src/utils/index.ts b/components/src/utils/index.ts index e1dae5ce..67945f91 100644 --- a/components/src/utils/index.ts +++ b/components/src/utils/index.ts @@ -15,7 +15,6 @@ export * from './axis'; export * from './browser-storage'; export * from './chart-actions'; export * from './combine-sx'; -export * from './component-ids'; export * from './data-field-interpolation'; export * from './format'; export * from './request-interpolation'; diff --git a/dashboards/src/components/Panel/Panel.tsx b/dashboards/src/components/Panel/Panel.tsx index fe4776ac..a073574e 100644 --- a/dashboards/src/components/Panel/Panel.tsx +++ b/dashboards/src/components/Panel/Panel.tsx @@ -13,19 +13,12 @@ import type { CardProps } from '@mui/material'; import { Card, CardContent } from '@mui/material'; -import { - ErrorAlert, - ErrorBoundary, - ItemActionsProvider, - SelectionProvider, - combineSx, - useId, -} from '@perses-dev/components'; +import { ErrorAlert, ErrorBoundary, ItemActionsProvider, SelectionProvider, combineSx } from '@perses-dev/components'; import type { ActionOptions } from '@perses-dev/plugin-system'; import { useDataQueriesContext, usePluginRegistry } from '@perses-dev/plugin-system'; import type { PanelDefinition } from '@perses-dev/spec'; import type { ComponentType, ReactNode } from 'react'; -import { memo, useEffect, useMemo, useState } from 'react'; +import { memo, useEffect, useId, useMemo, useState } from 'react'; import useResizeObserver from 'use-resize-observer'; import type { PanelGroupItemId } from '../../model'; @@ -96,7 +89,7 @@ export const Panel = memo(function Panel(props: PanelProps) { } = props; // Make sure we have an ID we can use for aria attributes - const generatedPanelId = useId('Panel'); + const generatedPanelId = `Panel-${useId()}`; const headerId = `${generatedPanelId}-header`; const [contentElement, setContentElement] = useState(null);