diff --git a/package.json b/package.json
index 5dfbc266..03be9ede 100644
--- a/package.json
+++ b/package.json
@@ -60,6 +60,7 @@
"rc-animate": "^2.9.1",
"react": "^v16.9.0-alpha.0",
"react-dom": "^v16.9.0-alpha.0",
+ "sinon": "^15.0.1",
"typescript": "^4.0.0"
},
"dependencies": {
diff --git a/src/Filler.tsx b/src/Filler.tsx
index 4628b22c..14f747fb 100644
--- a/src/Filler.tsx
+++ b/src/Filler.tsx
@@ -46,14 +46,19 @@ const Filler = React.forwardRef(
};
}
+ const handleResize = React.useCallback(
+ ({ offsetHeight }) => {
+ if (offsetHeight && onInnerResize) {
+ onInnerResize();
+ }
+ },
+ [onInnerResize],
+ );
+
return (
{
- if (offsetHeight && onInnerResize) {
- onInnerResize();
- }
- }}
+ onResize={handleResize}
>
(props: ListProps
, ref: React.Ref) {
[itemKey],
);
- const sharedConfig: SharedConfig = {
- getKey,
- };
+ const sharedConfig: SharedConfig = React.useMemo(() => {
+ return {
+ getKey,
+ };
+ }, [getKey]);
// ================================ Scroll ================================
function syncScrollTop(newTop: number | ((prev: number) => number)) {
diff --git a/src/hooks/useChildren.tsx b/src/hooks/useChildren.tsx
index d22ea4c0..47a9e0dd 100644
--- a/src/hooks/useChildren.tsx
+++ b/src/hooks/useChildren.tsx
@@ -10,17 +10,19 @@ export default function useChildren(
renderFunc: RenderFunc,
{ getKey }: SharedConfig,
) {
- return list.slice(startIndex, endIndex + 1).map((item, index) => {
- const eleIndex = startIndex + index;
- const node = renderFunc(item, eleIndex, {
- // style: status === 'MEASURE_START' ? { visibility: 'hidden' } : {},
- }) as React.ReactElement;
+ return React.useMemo(() => {
+ return list.slice(startIndex, endIndex + 1).map((item, index) => {
+ const eleIndex = startIndex + index;
+ const node = renderFunc(item, eleIndex, {
+ // style: status === 'MEASURE_START' ? { visibility: 'hidden' } : {},
+ }) as React.ReactElement;
- const key = getKey(item);
- return (
- - setNodeRef(item, ele)}>
- {node}
-
- );
- });
+ const key = getKey(item);
+ return (
+ - setNodeRef(item, ele)}>
+ {node}
+
+ );
+ });
+ }, [list, startIndex, endIndex, setNodeRef, renderFunc, getKey]);
}
diff --git a/src/hooks/useHeights.tsx b/src/hooks/useHeights.tsx
index 9a6bb832..5774b8bc 100644
--- a/src/hooks/useHeights.tsx
+++ b/src/hooks/useHeights.tsx
@@ -15,11 +15,11 @@ export default function useHeights(
const heightsRef = useRef(new CacheMap());
const collectRafRef = useRef();
- function cancelRaf() {
+ const cancelRaf = React.useCallback(function cancelRaf() {
raf.cancel(collectRafRef.current);
- }
+ }, []);
- function collectHeight() {
+ const collectHeight = React.useCallback(function () {
cancelRaf();
collectRafRef.current = raf(() => {
@@ -36,28 +36,31 @@ export default function useHeights(
// Always trigger update mark to tell parent that should re-calculate heights when resized
setUpdatedMark((c) => c + 1);
});
- }
-
- function setInstanceRef(item: T, instance: HTMLElement) {
- const key = getKey(item);
- const origin = instanceRef.current.get(key);
+ }, []);
- if (instance) {
- instanceRef.current.set(key, instance);
- collectHeight();
- } else {
- instanceRef.current.delete(key);
- }
+ const setInstanceRef = React.useCallback(
+ function (item: T, instance: HTMLElement) {
+ const key = getKey(item);
+ const origin = instanceRef.current.get(key);
- // Instance changed
- if (!origin !== !instance) {
if (instance) {
- onItemAdd?.(item);
+ instanceRef.current.set(key, instance);
+ collectHeight();
} else {
- onItemRemove?.(item);
+ instanceRef.current.delete(key);
+ }
+
+ // Instance changed
+ if (!origin !== !instance) {
+ if (instance) {
+ onItemAdd?.(item);
+ } else {
+ onItemRemove?.(item);
+ }
}
- }
- }
+ },
+ [getKey, onItemAdd, onItemRemove],
+ );
useEffect(() => {
return cancelRaf;
diff --git a/tests/props.test.js b/tests/props.test.js
index 45edeff2..7b596016 100644
--- a/tests/props.test.js
+++ b/tests/props.test.js
@@ -1,5 +1,6 @@
import React from 'react';
import { mount } from 'enzyme';
+import sinon from 'sinon';
import List from '../src';
describe('Props', () => {
@@ -11,33 +12,47 @@ describe('Props', () => {
}
const wrapper = mount(
- item.id}>
+ item.id}>
{({ id }) => {id}}
,
);
- expect(
- wrapper
- .find('Item')
- .at(0)
- .key(),
- ).toBe('903');
-
- expect(
- wrapper
- .find('Item')
- .at(1)
- .key(),
- ).toBe('1128');
+ expect(wrapper.find('Item').at(0).key()).toBe('903');
+
+ expect(wrapper.find('Item').at(1).key()).toBe('1128');
});
it('prefixCls', () => {
const wrapper = mount(
- id} prefixCls="prefix">
- {id => {id}
}
+ id} prefixCls="prefix">
+ {(id) => {id}
}
,
);
expect(wrapper.find('.prefix-holder-inner').length).toBeTruthy();
});
+
+ it('no unnecessary re-render', () => {
+ const renderItem = sinon.fake(({ id, key }) => {id}
);
+ const data = [{ id: 1, key: 1 }];
+ function Wrapper() {
+ const [state, setState] = React.useState(0);
+
+ React.useEffect(() => {
+ setState(1);
+ }, []);
+
+ return (
+
+
{state}
+
+ {renderItem}
+
+
+ );
+ }
+ const wrapper = mount();
+ expect(wrapper.find('h1').text()).toBe('1');
+ expect(renderItem.callCount).toBe(1);
+ });
});
diff --git a/tests/scroll.test.js b/tests/scroll.test.js
index ab5fe46b..eb6276d0 100644
--- a/tests/scroll.test.js
+++ b/tests/scroll.test.js
@@ -52,7 +52,9 @@ describe('List.Scroll', () => {
jest.useFakeTimers();
const listRef = React.createRef();
const wrapper = genList({ itemHeight: 20, height: 100, data: genData(100), ref: listRef });
- jest.runAllTimers();
+ act(() => {
+ jest.runAllTimers();
+ });
listRef.current.scrollTo(null);
expect(wrapper.find('.rc-virtual-list-scrollbar-thumb').props().style.display).not.toEqual(
@@ -65,8 +67,10 @@ describe('List.Scroll', () => {
it('value scroll', () => {
const listRef = React.createRef();
const wrapper = genList({ itemHeight: 20, height: 100, data: genData(100), ref: listRef });
- listRef.current.scrollTo(903);
- jest.runAllTimers();
+ act(() => {
+ listRef.current.scrollTo(903);
+ jest.runAllTimers();
+ });
expect(wrapper.find('ul').instance().scrollTop).toEqual(903);
wrapper.unmount();
@@ -79,40 +83,56 @@ describe('List.Scroll', () => {
describe('index scroll', () => {
it('work', () => {
- listRef.current.scrollTo({ index: 30, align: 'top' });
- jest.runAllTimers();
+ act(() => {
+ listRef.current.scrollTo({ index: 30, align: 'top' });
+ jest.runAllTimers();
+ });
expect(wrapper.find('ul').instance().scrollTop).toEqual(600);
});
it('out of range should not crash', () => {
expect(() => {
- listRef.current.scrollTo({ index: 99999999999, align: 'top' });
- jest.runAllTimers();
+ act(() => {
+ listRef.current.scrollTo({ index: 99999999999, align: 'top' });
+ jest.runAllTimers();
+ });
}).not.toThrow();
});
});
it('scroll top should not out of range', () => {
- listRef.current.scrollTo({ index: 0, align: 'bottom' });
- jest.runAllTimers();
+ act(() => {
+ listRef.current.scrollTo({ index: 0, align: 'bottom' });
+ jest.runAllTimers();
+ });
expect(wrapper.find('ul').instance().scrollTop).toEqual(0);
});
it('key scroll', () => {
- listRef.current.scrollTo({ key: '30', align: 'bottom' });
- jest.runAllTimers();
+ act(() => {
+ listRef.current.scrollTo({ key: '30', align: 'bottom' });
+ jest.runAllTimers();
+ });
expect(wrapper.find('ul').instance().scrollTop).toEqual(520);
});
it('smart', () => {
- listRef.current.scrollTo(0);
- listRef.current.scrollTo({ index: 30 });
- jest.runAllTimers();
+ act(() => {
+ listRef.current.scrollTo(0);
+ });
+ act(() => {
+ listRef.current.scrollTo({ index: 30 });
+ jest.runAllTimers();
+ });
expect(wrapper.find('ul').instance().scrollTop).toEqual(520);
- listRef.current.scrollTo(800);
- listRef.current.scrollTo({ index: 30 });
- jest.runAllTimers();
+ act(() => {
+ listRef.current.scrollTo(800);
+ });
+ act(() => {
+ listRef.current.scrollTo({ index: 30 });
+ jest.runAllTimers();
+ });
expect(wrapper.find('ul').instance().scrollTop).toEqual(600);
});
});
diff --git a/tests/touch.test.js b/tests/touch.test.js
index 12691ab7..49a4477f 100644
--- a/tests/touch.test.js
+++ b/tests/touch.test.js
@@ -1,4 +1,5 @@
import React from 'react';
+import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
import { spyElementPrototypes } from './utils/domHook';
import List from '../src';
@@ -55,22 +56,24 @@ describe('List.Touch', () => {
return wrapper.find('.rc-virtual-list-holder').instance();
}
- // start
- const touchEvent = new Event('touchstart');
- touchEvent.touches = [{ pageY: 100 }];
- getElement().dispatchEvent(touchEvent);
+ act(() => {
+ // start
+ const touchEvent = new Event('touchstart');
+ touchEvent.touches = [{ pageY: 100 }];
+ getElement().dispatchEvent(touchEvent);
- // move
- const moveEvent = new Event('touchmove');
- moveEvent.touches = [{ pageY: 90 }];
- getElement().dispatchEvent(moveEvent);
+ // move
+ const moveEvent = new Event('touchmove');
+ moveEvent.touches = [{ pageY: 90 }];
+ getElement().dispatchEvent(moveEvent);
- // end
- const endEvent = new Event('touchend');
- getElement().dispatchEvent(endEvent);
+ // end
+ const endEvent = new Event('touchend');
+ getElement().dispatchEvent(endEvent);
- // smooth
- jest.runAllTimers();
+ // smooth
+ jest.runAllTimers();
+ });
expect(wrapper.find('ul').instance().scrollTop > 10).toBeTruthy();
wrapper.unmount();
@@ -83,35 +86,40 @@ describe('List.Touch', () => {
return wrapper.find('.rc-virtual-list-holder').instance();
}
- // start
- const touchEvent = new Event('touchstart');
- touchEvent.touches = [{ pageY: 500 }];
- getElement().dispatchEvent(touchEvent);
-
- // move
const preventDefault = jest.fn();
- const moveEvent = new Event('touchmove');
- moveEvent.touches = [{ pageY: 0 }];
- moveEvent.preventDefault = preventDefault;
- getElement().dispatchEvent(moveEvent);
+
+ act(() => {
+ // start
+ const touchEvent = new Event('touchstart');
+ touchEvent.touches = [{ pageY: 500 }];
+ getElement().dispatchEvent(touchEvent);
+
+ // move
+ const moveEvent = new Event('touchmove');
+ moveEvent.touches = [{ pageY: 0 }];
+ moveEvent.preventDefault = preventDefault;
+ getElement().dispatchEvent(moveEvent);
+ });
// Call preventDefault
expect(preventDefault).toHaveBeenCalled();
- // ======= Not call since scroll to the bottom =======
- jest.runAllTimers();
- preventDefault.mockReset();
+ act(() => {
+ // ======= Not call since scroll to the bottom =======
+ jest.runAllTimers();
+ preventDefault.mockReset();
- // start
- const touchEvent2 = new Event('touchstart');
- touchEvent2.touches = [{ pageY: 500 }];
- getElement().dispatchEvent(touchEvent2);
+ // start
+ const touchEvent2 = new Event('touchstart');
+ touchEvent2.touches = [{ pageY: 500 }];
+ getElement().dispatchEvent(touchEvent2);
- // move
- const moveEvent2 = new Event('touchmove');
- moveEvent2.touches = [{ pageY: 0 }];
- moveEvent2.preventDefault = preventDefault;
- getElement().dispatchEvent(moveEvent2);
+ // move
+ const moveEvent2 = new Event('touchmove');
+ moveEvent2.touches = [{ pageY: 0 }];
+ moveEvent2.preventDefault = preventDefault;
+ getElement().dispatchEvent(moveEvent2);
+ });
expect(preventDefault).not.toHaveBeenCalled();
});
@@ -121,12 +129,11 @@ describe('List.Touch', () => {
const preventDefault = jest.fn();
const wrapper = genList({ itemHeight: 20, height: 100, data: genData(100) });
- const touchEvent = new Event('touchstart');
- touchEvent.preventDefault = preventDefault;
- wrapper
- .find('.rc-virtual-list-scrollbar')
- .instance()
- .dispatchEvent(touchEvent);
+ act(() => {
+ const touchEvent = new Event('touchstart');
+ touchEvent.preventDefault = preventDefault;
+ wrapper.find('.rc-virtual-list-scrollbar').instance().dispatchEvent(touchEvent);
+ });
expect(preventDefault).toHaveBeenCalled();
});