From 484cac79bfa6f408d833cfdd33c20dac33bf66ac Mon Sep 17 00:00:00 2001 From: Isaev Alexandr Date: Mon, 1 Apr 2024 15:34:29 +0300 Subject: [PATCH] fix(TreeList): add getId prop drilling to list container (#1469) Co-authored-by: Alexandr Isaev --- src/components/TreeList/TreeList.tsx | 5 +++-- src/components/TreeList/__stories__/TreeList.mdx | 6 +++--- .../__stories__/stories/WithDndListStory.tsx | 2 +- .../TreeListContainer/TreeListContainer.tsx | 4 +++- src/components/TreeList/types.ts | 10 +++++++--- src/components/TreeSelect/TreeSelect.tsx | 6 +++--- .../__stories__/components/WithDndListExample.tsx | 2 +- src/components/TreeSelect/types.ts | 2 +- .../useList/__stories__/components/ListWithDnd.tsx | 2 +- src/components/useList/__stories__/useList.mdx | 8 ++++---- .../ListRecursiveRenderer/ListRecursiveRenderer.tsx | 4 ++-- src/components/useList/hooks/useFlattenListItems.ts | 12 ++++++++---- src/components/useList/hooks/useList.ts | 8 ++++---- src/components/useList/hooks/useListParsedState.ts | 8 ++++---- src/components/useList/utils/flattenItems.ts | 4 ++-- src/components/useList/utils/getListItemId.ts | 8 ++++---- .../useList/utils/getListParsedState.test.ts | 2 +- src/components/useList/utils/getListParsedState.ts | 8 ++++---- 18 files changed, 56 insertions(+), 45 deletions(-) diff --git a/src/components/TreeList/TreeList.tsx b/src/components/TreeList/TreeList.tsx index ee94424692..720defff3e 100644 --- a/src/components/TreeList/TreeList.tsx +++ b/src/components/TreeList/TreeList.tsx @@ -21,7 +21,7 @@ export const TreeList = ({ activeItemId, selectedById, defaultGroupsExpanded = true, - getId, + getItemId, renderItem: propsRenderItem, renderContainer = TreeListContainer, onItemClick, @@ -37,7 +37,7 @@ export const TreeList = ({ const listParsedState = useList({ items, - getId, + getItemId, expandedById, disabledById, activeItemId, @@ -142,5 +142,6 @@ export const TreeList = ({ activeItemId, selectedById, renderItem, + getItemId, }); }; diff --git a/src/components/TreeList/__stories__/TreeList.mdx b/src/components/TreeList/__stories__/TreeList.mdx index 1a3a93f9b8..7a16ad7570 100644 --- a/src/components/TreeList/__stories__/TreeList.mdx +++ b/src/components/TreeList/__stories__/TreeList.mdx @@ -19,7 +19,7 @@ The basic component for working with lists, including tree-like ones. Under the - [multiple](#multiple); - [size](#size-available-options); - [defaultGroupsExpanded](#defaultgroupsexpanded); -- [getId](#getid); +- [getItemId](#getItemId); - [renderItem](#renderitem); - [renderContainer](#rendercontainer); - [onItemClick](#onitemclick); @@ -166,7 +166,7 @@ const Component = () => { }; ``` -### getId +### getItemId Generate an id for a list item depending on the list data. If it's necessary to have access to more custom management of the state of the list. The property is optional. @@ -176,7 +176,7 @@ const items = [ {data: {id: 'id-2', title: 'some title 2'}, children: [...]}, ]; - id} /> + id} /> ``` ### qa diff --git a/src/components/TreeList/__stories__/stories/WithDndListStory.tsx b/src/components/TreeList/__stories__/stories/WithDndListStory.tsx index bbd112e443..1c4f454d0d 100644 --- a/src/components/TreeList/__stories__/stories/WithDndListStory.tsx +++ b/src/components/TreeList/__stories__/stories/WithDndListStory.tsx @@ -148,7 +148,7 @@ export const WithDndListStory = (storyProps: WithDndListStoryProps) => { {...listState} mapItemDataToProps={({someRandomKey}) => ({title: someRandomKey})} // you can omit this prop here. If prop `id` passed, TreeSelect would take it by default - getId={({id}) => id} + getItemId={({id}) => id} onItemClick={({id, disabled, context: {groupState}}) => { if (!groupState && !disabled) { listState.setSelected((prevState) => ({ diff --git a/src/components/TreeList/components/TreeListContainer/TreeListContainer.tsx b/src/components/TreeList/components/TreeListContainer/TreeListContainer.tsx index b9d8e20ca0..71171c68d8 100644 --- a/src/components/TreeList/components/TreeListContainer/TreeListContainer.tsx +++ b/src/components/TreeList/components/TreeListContainer/TreeListContainer.tsx @@ -13,7 +13,8 @@ export const TreeListContainer = ({ renderItem, className, idToFlattenIndex, -}: TreeListRenderContainerProps & {className?: string}) => { + getItemId, +}: TreeListRenderContainerProps) => { return ( {items.map((itemSchema, index) => ( @@ -23,6 +24,7 @@ export const TreeListContainer = ({ itemSchema={itemSchema} index={index} expandedById={expandedById} + getItemId={getItemId} > {renderItem} diff --git a/src/components/TreeList/types.ts b/src/components/TreeList/types.ts index ebddd93be3..3ce531dbac 100644 --- a/src/components/TreeList/types.ts +++ b/src/components/TreeList/types.ts @@ -51,6 +51,12 @@ export type TreeListRenderContainerProps = ListParsedState & Partial & { id: string; size: ListItemSize; + containerRef?: React.RefObject; + className?: string; + /** + * Define custom id depended on item data value to use in controlled state component variant + */ + getItemId?(item: T): ListItemId; renderItem( id: ListItemId, index: number, @@ -59,8 +65,6 @@ export type TreeListRenderContainerProps = ListParsedState & */ renderContainerProps?: Object, ): React.JSX.Element; - containerRef?: React.RefObject; - className?: string; }; export type TreeListRenderContainer = ( @@ -89,7 +93,7 @@ export interface TreeListProps extends QAProps, Partial { /** * Define custom id depended on item data value to use in controlled state component variant */ - getId?(item: T): ListItemId; + getItemId?(item: T): ListItemId; /** * Override list item content by you custom node. */ diff --git a/src/components/TreeSelect/TreeSelect.tsx b/src/components/TreeSelect/TreeSelect.tsx index dd87129c9f..2028e17a3d 100644 --- a/src/components/TreeSelect/TreeSelect.tsx +++ b/src/components/TreeSelect/TreeSelect.tsx @@ -51,7 +51,7 @@ export const TreeSelect = React.forwardRef(function TreeSelect( defaultGroupsExpanded, onClose, onUpdate, - getId, + getItemId, onOpenChange, renderControl, renderItem = defaultItemRenderer as TreeListRenderItem, @@ -90,7 +90,7 @@ export const TreeSelect = React.forwardRef(function TreeSelect( const listParsedState = useList({ items, - getId, + getItemId, ...listState, }); @@ -240,7 +240,7 @@ export const TreeSelect = React.forwardRef(function TreeSelect( multiple={multiple} id={`list-${treeSelectId}`} containerRef={containerRef} - getId={getId} + getItemId={getItemId} disabledById={listState.disabledById} selectedById={listState.selectedById} expandedById={listState.expandedById} diff --git a/src/components/TreeSelect/__stories__/components/WithDndListExample.tsx b/src/components/TreeSelect/__stories__/components/WithDndListExample.tsx index b048b02286..f702fe10c4 100644 --- a/src/components/TreeSelect/__stories__/components/WithDndListExample.tsx +++ b/src/components/TreeSelect/__stories__/components/WithDndListExample.tsx @@ -150,7 +150,7 @@ export const WithDndListExample = (storyProps: WithDndListExampleProps) => { activeItemId={activeItemId} setActiveItemId={setActiveItemId} // you can omit this prop here. If prop `id` passed, TreeSelect would take it by default - getId={({id}) => id} + getItemId={({id}) => id} mapItemDataToProps={({someRandomKey}) => ({ title: someRandomKey, })} diff --git a/src/components/TreeSelect/types.ts b/src/components/TreeSelect/types.ts index 1aa3089873..ed9f8848c9 100644 --- a/src/components/TreeSelect/types.ts +++ b/src/components/TreeSelect/types.ts @@ -87,7 +87,7 @@ export interface TreeSelectProps extends QAProps, Partial title, + getItemId: ({title}) => title, items: filterState.items, ...listState, }); diff --git a/src/components/useList/__stories__/useList.mdx b/src/components/useList/__stories__/useList.mdx index 574c4f03bf..711b5b6d87 100644 --- a/src/components/useList/__stories__/useList.mdx +++ b/src/components/useList/__stories__/useList.mdx @@ -219,7 +219,7 @@ The main hook for creating a stateless version of the sheet. ``` - `expandedById` - state for open/closed `List` elements. Affects the formation of the `visibleFlattenIds` - if the element id in this object is set to `false` - all elements of this group and all nested groups will not be present in the final ids order; -- `getId` - the property is optional. Allows you to generate an id for a list item depending on the list data: +- `getItemId` - the property is optional. Allows you to generate an id for a list item depending on the list data: ```tsx const items = [ @@ -235,7 +235,7 @@ const items = [ */ const {byid} = useList({ items, - getId: ({id}) => id, + getItemId: ({id}) => id, }) ``` @@ -268,7 +268,7 @@ const {byid} = useList({ }; ``` - The default IDs are formed according to the principle `-`. To make a custom `id`, you need to use it either when forming an array of `items` or through the`getId` function. + The default IDs are formed according to the principle `-`. To make a custom `id`, you need to use it either when forming an array of `items` or through the`getItemId` function. - `groupsState` - a normalized representation of metadata about a group if the item is both a list item and a group: - `childrenIds` - list of child element IDs; @@ -473,7 +473,7 @@ For the virtualized version of the list, you need to implement a component with - `index` - the ordinal index of the first level of the sheet elements; - `expandedById` - state for hidden group elements, if the functionality of hiding/opening groups is supported - `className` - custom class name to mix with; -- `getId` - the property is optional. Allows you to generate an id for a list item depending on the list data: +- `getItemId` - the property is optional. Allows you to generate an id for a list item depending on the list data: - `style` - optional react `React.CSSProperties` object; ```tsx diff --git a/src/components/useList/components/ListRecursiveRenderer/ListRecursiveRenderer.tsx b/src/components/useList/components/ListRecursiveRenderer/ListRecursiveRenderer.tsx index 5689991c7e..5711469193 100644 --- a/src/components/useList/components/ListRecursiveRenderer/ListRecursiveRenderer.tsx +++ b/src/components/useList/components/ListRecursiveRenderer/ListRecursiveRenderer.tsx @@ -16,7 +16,7 @@ export interface ListRecursiveRendererProps extends Partial; } @@ -29,7 +29,7 @@ export function ListItemRecursiveRenderer({ ...props }: ListRecursiveRendererProps) { const groupedId = getGroupItemId(index, parentId); - const id = getListItemId({item: itemSchema, groupedId, getId: props.getId}); + const id = getListItemId({item: itemSchema, groupedId, getItemId: props.getItemId}); const node = props.children(id, props.idToFlattenIndex[id]); diff --git a/src/components/useList/hooks/useFlattenListItems.ts b/src/components/useList/hooks/useFlattenListItems.ts index b764306da6..6043e53ee9 100644 --- a/src/components/useList/hooks/useFlattenListItems.ts +++ b/src/components/useList/hooks/useFlattenListItems.ts @@ -7,7 +7,7 @@ import {flattenItems} from '../utils/flattenItems'; interface UseFlattenListItemsProps { items: ListItemType[]; expandedById?: Record; - getId?(item: T): ListItemId; + getItemId?(item: T): ListItemId; } /** @@ -15,10 +15,14 @@ interface UseFlattenListItemsProps { * Returns flatten ids list tree structure representation. * Not included items if they in `expandedById` map */ -export function useFlattenListItems({items, expandedById, getId}: UseFlattenListItemsProps) { +export function useFlattenListItems({ + items, + expandedById, + getItemId, +}: UseFlattenListItemsProps) { const order = React.useMemo(() => { - return flattenItems(items, expandedById, getId); - }, [items, expandedById, getId]); + return flattenItems(items, expandedById, getItemId); + }, [items, expandedById, getItemId]); return order; } diff --git a/src/components/useList/hooks/useList.ts b/src/components/useList/hooks/useList.ts index 1d18d9736e..b3153afb2a 100644 --- a/src/components/useList/hooks/useList.ts +++ b/src/components/useList/hooks/useList.ts @@ -15,7 +15,7 @@ export interface UseListProps extends Partial { /** * Control expanded items state from external source */ - getId?(item: T): ListItemId; + getItemId?(item: T): ListItemId; } export type UseListResult = ListParsedState & {initialState: InitialListParsedState}; @@ -23,10 +23,10 @@ export type UseListResult = ListParsedState & {initialState: InitialListPa /** * Take array of items as a argument and returns parsed representation of this data structure to work with */ -export const useList = ({items, expandedById, getId}: UseListProps): UseListResult => { +export const useList = ({items, expandedById, getItemId}: UseListProps): UseListResult => { const {itemsById, groupsState, itemsState, initialState} = useListParsedState({ items, - getId, + getItemId, }); const {visibleFlattenIds, idToFlattenIndex} = useFlattenListItems({ @@ -35,7 +35,7 @@ export const useList = ({items, expandedById, getId}: UseListProps): UseLi * By default controlled from list items declaration state */ expandedById: expandedById || initialState.expandedById, - getId, + getItemId, }); return { diff --git a/src/components/useList/hooks/useListParsedState.ts b/src/components/useList/hooks/useListParsedState.ts index 52d3403822..cfa26652f4 100644 --- a/src/components/useList/hooks/useListParsedState.ts +++ b/src/components/useList/hooks/useListParsedState.ts @@ -9,17 +9,17 @@ interface UseListParsedStateProps { /** * List item id dependant of data */ - getId?(item: T): ListItemId; + getItemId?(item: T): ListItemId; } /** * From the tree structure of list items we get meta information and * flatten list in right order without taking elements that hidden in expanded groups */ -export function useListParsedState({items, getId}: UseListParsedStateProps) { +export function useListParsedState({items, getItemId}: UseListParsedStateProps) { const result = React.useMemo(() => { - return getListParsedState(items, getId); - }, [getId, items]); + return getListParsedState(items, getItemId); + }, [getItemId, items]); return result; } diff --git a/src/components/useList/utils/flattenItems.ts b/src/components/useList/utils/flattenItems.ts index d60f1760b0..54fb0e98de 100644 --- a/src/components/useList/utils/flattenItems.ts +++ b/src/components/useList/utils/flattenItems.ts @@ -7,7 +7,7 @@ import {isTreeItemGuard} from './isTreeItemGuard'; export function flattenItems( items: ListItemType[], expandedById: Record = {}, - getId?: (item: T) => ListItemId, + getItemId?: (item: T) => ListItemId, ): ParsedFlattenState { if (process.env.NODE_ENV !== 'production') { console.time('flattenItems'); @@ -20,7 +20,7 @@ export function flattenItems( parentId?: string, ) => { const groupedId = getGroupItemId(index, parentId); - const id = getListItemId({groupedId, item, getId}); + const id = getListItemId({groupedId, item, getItemId}); order.push(id); diff --git a/src/components/useList/utils/getListItemId.ts b/src/components/useList/utils/getListItemId.ts index 78d101075e..9578c96bfe 100644 --- a/src/components/useList/utils/getListItemId.ts +++ b/src/components/useList/utils/getListItemId.ts @@ -5,14 +5,14 @@ import {isTreeItemGuard} from './isTreeItemGuard'; interface GetListItemIdProps { item: ListItemType; groupedId: ListItemId; - getId?(data: T): ListItemId; + getItemId?(data: T): ListItemId; } -export const getListItemId = ({item, groupedId, getId}: GetListItemIdProps) => { +export const getListItemId = ({item, groupedId, getItemId}: GetListItemIdProps) => { let id = groupedId; - if (typeof getId === 'function') { - id = getId(isTreeItemGuard(item) ? item.data : item); + if (typeof getItemId === 'function') { + id = getItemId(isTreeItemGuard(item) ? item.data : item); } else if (item && typeof item === 'object' && 'id' in item && item.id) { id = item.id; } diff --git a/src/components/useList/utils/getListParsedState.test.ts b/src/components/useList/utils/getListParsedState.test.ts index 9122c4cf29..e893904422 100644 --- a/src/components/useList/utils/getListParsedState.test.ts +++ b/src/components/useList/utils/getListParsedState.test.ts @@ -123,7 +123,7 @@ describe('getListParsedState', () => { }); }); - test('get expected result with getId function passed', () => { + test('get expected result with getItemId function passed', () => { const data: ListItemType<{title: string; id: string}>[] = [ { data: {title: 'item-0', id: 'id-1'}, diff --git a/src/components/useList/utils/getListParsedState.ts b/src/components/useList/utils/getListParsedState.ts index 9c2cc81b23..e675209966 100644 --- a/src/components/useList/utils/getListParsedState.ts +++ b/src/components/useList/utils/getListParsedState.ts @@ -20,7 +20,7 @@ interface TraverseTreeItemProps { * For example T is entity type with id what represents db id * So now you can use it id as a list item id in internal state */ - getId?(item: T): ListItemId; + getItemId?(item: T): ListItemId; item: ListTreeItemType; index: number; parentId?: ListItemId; @@ -37,7 +37,7 @@ export function getListParsedState( * For example T is entity type with id what represents db id * So now you can use it id as a list item id in internal state */ - getId?: (item: T) => ListItemId, + getItemId?: (item: T) => ListItemId, ): ListParsedStateResult { if (process.env.NODE_ENV !== 'production') { console.time('getListParsedState'); @@ -55,7 +55,7 @@ export function getListParsedState( }; const traverseItem = ({item, index}: TraverseItemProps) => { - const id = getListItemId({groupedId: String(index), item, getId}); + const id = getListItemId({groupedId: String(index), item, getItemId}); result.itemsById[id] = item; @@ -83,7 +83,7 @@ export function getListParsedState( parentId, }: TraverseTreeItemProps) => { const groupedId = getGroupItemId(index, parentGroupedId); - const id = getListItemId({groupedId, item, getId}); + const id = getListItemId({groupedId, item, getItemId}); if (parentId) { result.groupsState[parentId].childrenIds.push(id);