react-drag-drop-files icon indicating copy to clipboard operation
react-drag-drop-files copied to clipboard

Narrow handle function types based on "multiple" prop

Open iGoodie opened this issue 9 months ago • 0 comments

Hey! 👋🏼 Thanks a lot for implementing such a time-saver library!

When multiple prop is set to a falsy value, it is guaranteed to have exactly 1 file. However the typings on onDrop, onSelect and handleChange are widely typed to accommodate both cases. I'd expect them to be narrowed to accept File as their first argument when multiple is set to falsy value.


Current types:

<FileUploader
	onDrop={file => {}}
	// ^? (property) onDrop?: ((arg0: File | Array<File>) => void)
/>

<FileUploader
	multiple={false}
	onDrop={file => {}}
	// ^? (property) onDrop?: ((arg0: File | Array<File>) => void)
/>

<FileUploader
	multiple
	onDrop={files => {}}
	// ^? (property) onDrop?: ((arg0: File | Array<File>) => void)
/>

Expected narrowed types:

<FileUploader
	onDrop={file => {}}
	// ^? (property) onDrop?: ((file: File) => void)
/>

<FileUploader
	multiple={false}
	onDrop={file => {}}
	// ^? (property) onDrop?: ((file: File) => void)
/>

<FileUploader
	multiple
	onDrop={files => {}}
	// ^? (property) onDrop?: ((files: Array<File>) => void)
/>

This could be achieved by having Props implemented as a generic type. Theoretically with something like:

type FileFn<M extends boolean> = [boolean] extends [M]
  ? (arg: File | File[]) => void
  : M extends true
  ? (files: File[]) => void
  : (file: File) => void;

type Props<M extends boolean = false> = {
  multiple?: M;
  onDrop?: FileFn<M>;
  onSelect?: FileFn<M>;
  handleChange?: FileFn<M>;
  // ... other props are omitted
};

function FileUploader<M extends boolean = false>(props: Props<M>) {
  return <>{/* ... code is omitted. Returning props instead, to be able to test */}</>;
}

Tests:

type SingleTest = FileFn<false>;
//   ^? type SingleTest = (file: File) => void;

type MultiTest = FileFn<true>;
//   ^? type MultiTest = (files: File[]) => void

const dynamicValue = Math.random() >= 0.5;
<FileUploader multiple={dynamicValue} onDrop={} />;
//                                                               ^? (arg: File | File[]) => void

<FileUploader multiple handleChange={} />;
//                                   ^? (property) handleChange?: ((files: File[]) => void)

<FileUploader multiple={false} handleChange={} />;
//                                               ^? (property) handleChange?: ((file: File) => void)

<FileUploader handleChange={} />;
//                     ^? (property) handleChange?: ((file: File) => void)

iGoodie avatar May 03 '24 10:05 iGoodie