diff --git a/.changeset/tidy-tags-update.md b/.changeset/tidy-tags-update.md new file mode 100644 index 000000000..e6c587a31 --- /dev/null +++ b/.changeset/tidy-tags-update.md @@ -0,0 +1,5 @@ +--- +'@keystar/ui': patch +--- + +Fix `TagGroup` not showing items added after the initial render when `maxRows` is set diff --git a/design-system/pkg/src/tag/TagGroup.tsx b/design-system/pkg/src/tag/TagGroup.tsx index 1e6c9836e..0ca0c4e80 100644 --- a/design-system/pkg/src/tag/TagGroup.tsx +++ b/design-system/pkg/src/tag/TagGroup.tsx @@ -1,7 +1,6 @@ import React, { type ForwardedRef, type ReactElement, - useCallback, useEffect, useMemo, useRef, @@ -14,8 +13,9 @@ import { useLocalizedStringFormatter } from 'react-aria/useLocalizedStringFormat import { ListKeyboardDelegate } from 'react-aria/ListKeyboardDelegate'; import { AriaTagGroupProps, useTagGroup } from 'react-aria/useTagGroup'; import { useId } from 'react-aria/useId'; -import { useLayoutEffect } from 'react'; import { useObjectRef } from 'react-aria/useObjectRef'; +import { useEffectEvent } from 'react-aria/private/utils/useEffectEvent'; +import { useLayoutEffect } from 'react-aria/private/utils/useLayoutEffect'; import { useResizeObserver } from 'react-aria/private/utils/useResizeObserver'; import { useValueEffect } from 'react-aria/private/utils/useValueEffect'; import { ListCollection } from 'react-stately/private/list/ListCollection'; @@ -69,7 +69,6 @@ function TagGroup( // props = useFormProps(props); let { maxRows, - children, actionLabel, onAction, renderEmptyState: renderEmptyStateProp, @@ -124,7 +123,7 @@ function TagGroup( const actionsId = useId(); const actionsRef = useRef(null); - let updateVisibleTagCount = useCallback(() => { + let updateVisibleTagCount = () => { if (maxRows && maxRows > 0) { let computeVisibleTagCount = () => { const containerEl = containerRef.current; @@ -205,18 +204,21 @@ function TagGroup( yield computeVisibleTagCount(); }); } - }, [maxRows, setTagState, direction, state.collection.size]); + }; + + let updateVisibleTagCountEffect = useEffectEvent(updateVisibleTagCount); useResizeObserver({ ref: containerRef, onResize: updateVisibleTagCount }); - // we only want this effect to run when children change - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/exhaustive-deps - useLayoutEffect(updateVisibleTagCount, [children]); + useLayoutEffect(() => { + if (state.collection.size > 0 && maxRows != null && maxRows > 0) { + queueMicrotask(updateVisibleTagCountEffect); + } + }, [state.collection.size, maxRows, updateVisibleTagCountEffect]); useEffect(() => { // Recalculate visible tags when fonts are loaded. - document.fonts?.ready.then(() => updateVisibleTagCount()); + document.fonts?.ready.then(() => updateVisibleTagCountEffect()); // we strictly want this effect to only run once // eslint-disable-next-line react-compiler/react-compiler diff --git a/design-system/pkg/src/tag/test/TagGroup.test.tsx b/design-system/pkg/src/tag/test/TagGroup.test.tsx index e0c2499ca..099d86fe0 100644 --- a/design-system/pkg/src/tag/test/TagGroup.test.tsx +++ b/design-system/pkg/src/tag/test/TagGroup.test.tsx @@ -68,4 +68,37 @@ describe('tag/TagGroup', function () { let tags = getAllByRole('row'); expect(tags[0]).toHaveAttribute('tabIndex', '0'); }); + + it('shows items added with a stable render function', () => { + let renderItem = (item: { id: number; label: string }) => ( + {item.label} + ); + let { getAllByRole, rerender } = renderWithProvider( + + {renderItem} + + ); + + rerender( + + {renderItem} + + ); + act(() => { + jest.runAllTicks(); + }); + + expect(getAllByRole('row')).toHaveLength(2); + }); });