Browse Source

fix: deepclone items when getOverflowItem to Prevent props.items from being changed internally due to rapid clicks (#2423)

* fix: deepclone items when getOverflowItem to Prevent props.items from being changed internally due to rapid clicks
* fix: change clonedeep to fast-copy copy
YannLynn 1 year ago
parent
commit
cf816b24b4

+ 6 - 4
packages/semi-foundation/overflowList/foundation.ts

@@ -1,6 +1,6 @@
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
 import { strings } from './constants';
-import { noop, get } from 'lodash';
+import { get, cloneDeep } from 'lodash';
 import copy from 'fast-copy';
 
 const Boundary = strings.BOUNDARY_MAP;
@@ -33,13 +33,15 @@ class OverflowListFoundation extends BaseFoundation<OverflowListAdapter> {
             return overflow;
         }
 
-        const visibleStateArr = items.map(({ key }: { key: string }) => Boolean(visibleState.get(key)));
+        const cloneItems = copy(items);
+
+        const visibleStateArr = cloneItems.map(({ key }: { key: string }) => Boolean(visibleState.get(key)));
         const visibleStart = visibleStateArr.indexOf(true);
         const visibleEnd = visibleStateArr.lastIndexOf(true);
 
         const overflowList = [];
-        overflowList[0] = visibleStart >= 0 ? items.slice(0, visibleStart) : [];
-        overflowList[1] = visibleEnd >= 0 ? items.slice(visibleEnd + 1, items.length) : items;
+        overflowList[0] = visibleStart >= 0 ? cloneItems.slice(0, visibleStart) : [];
+        overflowList[1] = visibleEnd >= 0 ? cloneItems.slice(visibleEnd + 1, cloneItems.length) : cloneItems;
         return overflowList;
     }
 

+ 8 - 7
packages/semi-ui/overflowList/index.tsx

@@ -10,7 +10,8 @@ import IntersectionObserver from './intersectionObserver';
 import OverflowListFoundation, { OverflowListAdapter } from '@douyinfe/semi-foundation/overflowList/foundation';
 
 import '@douyinfe/semi-foundation/overflowList/overflowList.scss';
-import { cloneDeep, getDefaultPropsFromGlobalConfig } from '../_utils';
+import { getDefaultPropsFromGlobalConfig } from '../_utils';
+import copy from 'fast-copy';
 
 const prefixCls = cssClasses.PREFIX;
 const Boundary = strings.BOUNDARY_MAP;
@@ -20,7 +21,7 @@ const RenderMode = strings.MODE_MAP;
 export type { ReactIntersectionObserverProps } from './intersectionObserver';
 export type OverflowItem = Record<string, any>;
 
-type Key = string|number
+type Key = string | number
 
 export interface OverflowListProps {
     className?: string;
@@ -38,7 +39,7 @@ export interface OverflowListProps {
     wrapperStyle?: CSSProperties;
     itemKey?: Key | ((item: OverflowItem) => Key);
     onVisibleStateChange?: (visibleState: Map<string, boolean>) => void;
-    overflowRenderDirection?: "both"|"start"|'end' // used in tabs, not exposed to user
+    overflowRenderDirection?: "both" | "start" | 'end' // used in tabs, not exposed to user
 }
 
 export interface OverflowListState {
@@ -134,8 +135,8 @@ class OverflowList extends BaseComponent<OverflowListProps, OverflowListState> {
                 }
 
                 const isCollapseFromStart = props.collapseFrom === Boundary.START;
-                const visible = isCollapseFromStart ? cloneDeep(props.items).reverse().slice(0, maxCount) : props.items.slice(0, maxCount);
-                const overflow = isCollapseFromStart ? cloneDeep(props.items).reverse().slice(maxCount) : props.items.slice(maxCount);
+                const visible = isCollapseFromStart ? copy(props.items).reverse().slice(0, maxCount) : props.items.slice(0, maxCount);
+                const overflow = isCollapseFromStart ? copy(props.items).reverse().slice(maxCount) : props.items.slice(maxCount);
                 newState.visible = visible;
                 newState.overflow = overflow;
                 newState.maxCount = maxCount;
@@ -150,7 +151,7 @@ class OverflowList extends BaseComponent<OverflowListProps, OverflowListState> {
         return {
             ...super.adapter,
             updateVisibleState: (visibleState): void => {
-                this.setState({ visibleState }, ()=>{
+                this.setState({ visibleState }, () => {
                     this.props.onVisibleStateChange?.(visibleState);
                 });
             },
@@ -266,7 +267,7 @@ class OverflowList extends BaseComponent<OverflowListProps, OverflowListState> {
         }
         const inner =
             renderMode === RenderMode.SCROLL ?
-                (()=>{
+                (() => {
                     const list = [<div
                         className={cls(wrapperClassName, `${prefixCls}-scroll-wrapper`)}
                         ref={(ref): void => {

+ 12 - 0
packages/semi-ui/tabs/_story/tabs.stories.jsx

@@ -1058,4 +1058,16 @@ export const ShowRestInDropdownDemo = () => {
       ))}
     </Tabs>
   )
+}
+
+export const Fix2415 = () => {
+  return (
+     <Tabs style={{ width: 250, margin: '20px' }} type="card" collapsible>
+        {[10,2324325324324,1111].map(i => (
+            <TabPane tab={`Tab-${i}`} itemKey={`Tab-${i}`} key={i}>
+                Content of card tab {i}.Quickly click the right arrow and observe that the arrow is disabled correctly.
+            </TabPane>   
+        ))}    
+    </Tabs>
+  )
 }