Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(FileUploader): expose ref to clear files #18267

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

/* eslint-disable no-console */

import React from 'react';
import React, { useRef } from 'react';
import ExampleDropContainerApp from './stories/drop-container';
import ExampleDropContainerAppSingle from './stories/drag-and-drop-single';

Expand All @@ -32,6 +32,29 @@ export default {
},
};

export const TESTRefFileUploader = () => {
const ref = useRef(null);
return (
<div className="cds--file__container">
<button onClick={() => ref.current?.clearFiles()}>remove</button>
<FileUploader
ref={ref}
labelTitle="Upload files"
labelDescription="Max file size is 500 MB. Only .jpg files are supported."
buttonLabel="Add file"
buttonKind="primary"
size="md"
filenameStatus="edit"
accept={['.jpg', '.png']}
multiple={true}
disabled={false}
iconDescription="Delete file"
name=""
/>
</div>
);
};

export const Default = () => {
return (
<div className="cds--file__container">
Expand Down
34 changes: 18 additions & 16 deletions packages/react/src/components/FileUploader/FileUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { useState, ForwardedRef, ReactElement } from 'react';
import React, { useState, ForwardedRef, useImperativeHandle } from 'react';
import Filename from './Filename';
import FileUploaderButton from './FileUploaderButton';
import { ButtonKinds } from '../../prop-types/types';
import { ButtonKinds } from '../Button/Button';
import { keys, matches } from '../../internal/keyboard';
import { usePrefix } from '../../internal/usePrefix';
import { ReactAttr } from '../../types/common';
Expand Down Expand Up @@ -107,8 +107,15 @@ export interface FileUploaderProps extends ReactAttr<HTMLSpanElement> {
size?: 'sm' | 'small' | 'md' | 'field' | 'lg';
}

export interface FileUploaderHandle {
/**
* Clear internal state
*/
clearFiles: () => void;
}

const FileUploader = React.forwardRef(
<ItemType,>(
(
{
accept,
buttonKind,
Expand All @@ -127,15 +134,14 @@ const FileUploader = React.forwardRef(
size,
...other
}: FileUploaderProps,
ref: ForwardedRef<HTMLButtonElement>
ref: ForwardedRef<FileUploaderHandle>
) => {
const fileUploaderInstanceId = useId('file-uploader');
const [state, updateState] = useState({
fileNames: [] as (string | undefined)[],
});
const nodes: HTMLElement[] = [];
const prefix = usePrefix();

const handleChange = (evt) => {
evt.stopPropagation();
const filenames = Array.prototype.map.call(
Expand Down Expand Up @@ -169,10 +175,11 @@ const FileUploader = React.forwardRef(
}
};

const clearFiles = () => {
// A clearFiles function that resets filenames and can be referenced using a ref by the parent.
updateState({ fileNames: [] });
};
useImperativeHandle(ref, () => ({
clearFiles() {
updateState({ fileNames: [] });
},
}));

const uploaderButton = React.createRef<HTMLLabelElement>();
const classes = classNames({
Expand Down Expand Up @@ -255,12 +262,7 @@ const FileUploader = React.forwardRef(
</div>
);
}
) as {
<ItemType>(props: FileUploaderProps): ReactElement;
propTypes?: any;
contextTypes?: any;
defaultProps?: any;
};
);

FileUploader.propTypes = {
/**
Expand Down Expand Up @@ -342,6 +344,6 @@ FileUploader.propTypes = {
* sizes.
*/
size: PropTypes.oneOf(['sm', 'md', 'lg']),
};
} as PropTypes.ValidationMap<FileUploaderProps>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was getting TS errors and added this as a workaround:

accept: Type '(string | null | undefined)[]' is not assignable to type 'string[]'

filenameStatus: Type 'string' is not assignable to type '"edit" | "complete" | "uploading"'.


export default FileUploader;
Original file line number Diff line number Diff line change
Expand Up @@ -50,38 +50,22 @@ describe('FileUploader', () => {

it('should clear all uploaded files when `clearFiles` is called on a ref', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This unit test seems wrong it was trying to test if remove button works. I reverted back to a version from history that actually test clearFiles() function

const ref = React.createRef();
const onClick = jest.fn();
let requiredProps1 = {
...requiredProps,
filenameStatus: 'edit',
};
const fileUpload = render(
<FileUploader {...requiredProps1} ref={ref} onClick={onClick} />
);
const { container } = render(<FileUploader {...requiredProps} ref={ref} />);
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const input = fileUpload.container.querySelector('input');
const input = container.querySelector('input');

const filename = 'test.png';
act(() => {
uploadFiles(input, [new File(['test'], filename, { type: 'image/png' })]);
});
expect(getByText(fileUpload.container, filename)).toBeInstanceOf(
HTMLElement
);

const onDelete = jest.fn();
const description = 'test-description';
// eslint-disable-next-line testing-library/render-result-naming-convention

let removeFile = getByLabel(
fileUpload.container,
'test description - test.png'
);
// eslint-disable-next-line testing-library/prefer-screen-queries
expect(getByText(container, filename)).toBeInstanceOf(HTMLElement);
act(() => {
Simulate.click(removeFile);
ref.current.clearFiles();
});

expect(onClick).toHaveBeenCalledTimes(1);
// eslint-disable-next-line testing-library/prefer-screen-queries
expect(getByText(container, filename)).not.toBeInstanceOf(HTMLElement);
});

it('should synchronize the filename status state when its prop changes', () => {
Expand Down
Loading