From ad312b23a28e559fa21518c9f67ea0ba583e65a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?= =?UTF-8?q?=E5=8D=9C?= Date: Tue, 9 Aug 2022 16:29:48 +0800 Subject: [PATCH] fix: Upload in React 18 sync problem (#36968) * fix: sync flush * test: test case * test: fix deps --- components/upload/Upload.tsx | 7 ++- components/upload/__tests__/upload.test.js | 57 +++++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/components/upload/Upload.tsx b/components/upload/Upload.tsx index bae03963ac..0e619fa7da 100644 --- a/components/upload/Upload.tsx +++ b/components/upload/Upload.tsx @@ -3,6 +3,7 @@ import type { UploadProps as RcUploadProps } from 'rc-upload'; import RcUpload from 'rc-upload'; import useMergedState from 'rc-util/lib/hooks/useMergedState'; import * as React from 'react'; +import { flushSync } from 'react-dom'; import { ConfigContext } from '../config-provider'; import DisabledContext from '../config-provider/DisabledContext'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; @@ -101,7 +102,11 @@ const InternalUpload: React.ForwardRefRenderFunction = (pr cloneList = cloneList.slice(0, maxCount); } - setMergedFileList(cloneList); + // Prevent React18 auto batch since input[upload] trigger process at same time + // which makes fileList closure problem + flushSync(() => { + setMergedFileList(cloneList); + }); const changeInfo: UploadChangeParam = { file: file as UploadFile, diff --git a/components/upload/__tests__/upload.test.js b/components/upload/__tests__/upload.test.js index beec718b9a..9900923997 100644 --- a/components/upload/__tests__/upload.test.js +++ b/components/upload/__tests__/upload.test.js @@ -914,7 +914,7 @@ describe('Upload', () => { throw new TypeError("Object doesn't support this action"); }; - jest.spyOn(global, 'File').mockImplementationOnce(fileConstructor); + const spyIE = jest.spyOn(global, 'File').mockImplementationOnce(fileConstructor); fireEvent.change(container.querySelector('input'), { target: { files: [{ file: 'foo.png' }], @@ -925,6 +925,7 @@ describe('Upload', () => { await sleep(); expect(onChange.mock.calls[0][0].fileList).toHaveLength(1); + spyIE.mockRestore(); }); // https://github.com/ant-design/ant-design/issues/33819 @@ -965,4 +966,58 @@ describe('Upload', () => { const { container: wrapper2 } = render(); expect(wrapper2.querySelectorAll('.custom-upload-list').length).toBeGreaterThan(0); }); + + // https://github.com/ant-design/ant-design/issues/36869 + it('Prevent auto batch', async () => { + const mockFile1 = new File(['bamboo'], 'bamboo.png', { + type: 'image/png', + }); + const mockFile2 = new File(['light'], 'light.png', { + type: 'image/png', + }); + + let info1; + let info2; + + const onChange = jest.fn(); + const { container } = render( + { + if (info.file === mockFile1) { + info1 = info; + } else { + info2 = info; + } + }} + onChange={onChange} + />, + ); + + fireEvent.change(container.querySelector('input'), { + target: { + files: [mockFile1, mockFile2], + }, + }); + + // React 18 is async now + await act(async () => { + await sleep(); + }); + onChange.mockReset(); + + // Processing + act(() => { + info1.onProgress({ percent: 10 }, mockFile1); + info2.onProgress({ percent: 20 }, mockFile2); + }); + + expect(onChange).toHaveBeenCalledWith( + expect.objectContaining({ + fileList: [ + expect.objectContaining({ percent: 10 }), + expect.objectContaining({ percent: 20 }), + ], + }), + ); + }); });