fix: Upload in React 18 sync problem (#36968)

* fix: sync flush

* test: test case

* test: fix deps
This commit is contained in:
二货爱吃白萝卜 2022-08-09 16:29:48 +08:00 committed by GitHub
parent 74d76038ad
commit ad312b23a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 62 additions and 2 deletions

View File

@ -3,6 +3,7 @@ import type { UploadProps as RcUploadProps } from 'rc-upload';
import RcUpload from 'rc-upload'; import RcUpload from 'rc-upload';
import useMergedState from 'rc-util/lib/hooks/useMergedState'; import useMergedState from 'rc-util/lib/hooks/useMergedState';
import * as React from 'react'; import * as React from 'react';
import { flushSync } from 'react-dom';
import { ConfigContext } from '../config-provider'; import { ConfigContext } from '../config-provider';
import DisabledContext from '../config-provider/DisabledContext'; import DisabledContext from '../config-provider/DisabledContext';
import LocaleReceiver from '../locale-provider/LocaleReceiver'; import LocaleReceiver from '../locale-provider/LocaleReceiver';
@ -101,7 +102,11 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
cloneList = cloneList.slice(0, maxCount); 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<UploadFile> = { const changeInfo: UploadChangeParam<UploadFile> = {
file: file as UploadFile, file: file as UploadFile,

View File

@ -914,7 +914,7 @@ describe('Upload', () => {
throw new TypeError("Object doesn't support this action"); 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'), { fireEvent.change(container.querySelector('input'), {
target: { target: {
files: [{ file: 'foo.png' }], files: [{ file: 'foo.png' }],
@ -925,6 +925,7 @@ describe('Upload', () => {
await sleep(); await sleep();
expect(onChange.mock.calls[0][0].fileList).toHaveLength(1); expect(onChange.mock.calls[0][0].fileList).toHaveLength(1);
spyIE.mockRestore();
}); });
// https://github.com/ant-design/ant-design/issues/33819 // https://github.com/ant-design/ant-design/issues/33819
@ -965,4 +966,58 @@ describe('Upload', () => {
const { container: wrapper2 } = render(<Upload prefixCls="custom-upload" />); const { container: wrapper2 } = render(<Upload prefixCls="custom-upload" />);
expect(wrapper2.querySelectorAll('.custom-upload-list').length).toBeGreaterThan(0); 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(
<Upload
customRequest={info => {
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 }),
],
}),
);
});
}); });