The file input can be used to drag & drop or select files to upload. Unlike the basic HTML file input, FileInput
can be either controlled or uncontrolled.
FileInput
behaves much like a standard HTML file input, taking the same props for the most part. The main differences are in the accept prop, which is an array instead of a comma-delimited list, and the value
prop, which the standard input doesn’t have because it is always uncontrolled.
Additionally, in events files are accessed through target.value
instead of target.files
. Changes to the file list are always reflected via the onChange
event, regardless of whether they were added using the normal file dialog, by drag-and-drop, or removed using the file list’s delete button.
Once files have been selected they are displayed in a list, styled according to their status and with any file-specific error messages displayed. Each file in the list also has a delete button that can be used to remove it from the value.
value
prop & event.target.value
In order to make working with files easier, the value
prop and change/focus/blur event target values are an array of the following type:
interface FileObject {
/* A global Javascript File object. */
file: File
/* Defaults to 'unloaded', unless the file violates the `accept` constraint. */
status: 'unloaded' | 'loading' | 'loaded' | 'error'
/* File contents, if the file has been loaded. */
contents?: unknown
/* Any Error object raised during loading, or violation of the `accept` constraint. */
error?: {
name: string
message: string
}
/* A custom error message (possibly localized) that will be displayed to the user instead of `error.message`. */
errorMsg?: React.ReactNode
/* Loads the file and sets the status, contents, and error properties as appropriate. */
load: (l: (f: File) => Promise<unknown>) => Promise<unknown>
}
value
is always an array, though if multiple
is not true, it should never have more than one object in it. For smoother interop with certain form libraries and other input types, passing value as the empty string is also considered equivalent to an empty array.
If you get File objects from another source and want to pass them into a FileInput, you can use the FileInput.toFileObj(file: File)
function to create compatible FileObject wrappers.
The FileObject.load()
method can be used to asynchronously load the contents of a file. It takes a single optional argument, a function that does most of the work of actually loading the file: this load function should accept a File object and return a Promise that resolves to the file contents, or rejects to an Error describing why the file could not be loaded. The default load function utilizes a FileReader to read the file contents as a base64-encoded string.
Although the passed-in load function does most of the work, the load()
method is still recommended for its extra functionality:
unloaded
; otherwise it returns a Promise with the results of the previous load attempt.status
, contents
, and error
properties of the FileObject
it’s called on, depending on whether the Promise resolves or rejects. (It still returns the Promise in case you need to do additional or custom handling.)value
prop.Note that because file loading is typically async, care should be taken to avoid race conditions between load events and change events. Here’s an example where a form is submitted with only the file contents:
import React, { useCallback, useState } from 'react'
import { FileInput, Button } from '@repay/cactus-web'
const FileForm = () => {
const [state, setState] = useState({})
const [files, setFiles] = useState([])
const handleChange = useCallback(
(event) => {
const { name, value } = event.target
setFiles(value)
const promises = value.map(file => file.load().catch(console.error))
Promise.all(promises).then(files => {
setState(s => ({ ...s, [name]: files }))
})
},
[],
)
const handleSubmit = useCallback(
event => {
event.preventDefault()
sendDataToAPI(state)
},
[state]
)
return (
<form onSubmit={handleSubmit}>
<FileInput
name="file-input"
value={files}
accept={['.txt', '.doc']}
onChange={handleChange}
multiple
/>
<Button type="submit" variant="action">
Submit
</Button>
</form>
)
}
File errors, either violations of the accept
prop or errors occurring during a load attempt, are displayed in the file list. By default this means the FileObject.error.message
, but you may feel some errors are too technical for the average user; or you may wish to localize error messages. In any case, you can override the error message by setting FileObject.errorMsg
, which will then be displayed to the user instead.
For example, you might write something like this:
const onChange = (e) => {
setFiles(e.target.value)
const loading = e.target.value.map(f => f.load().then(
() => f,
(e) => {
if (e.name === 'SecurityError')
f.errorMsg = "You don't have permission to be aboard there, mate."
return f
},
))
// Compare to avoid race conditions, in case the files have changed since load started.
Promise.all(loading).then((new) => setFiles((old) => old === new ? [...new] : old))
}
FileInput
has several props for controlling text content & labels on the component. They can be used for simple customization, or localization.
buttonText
(“Select Files…“) - Used as the contents of the button that opens the file select dialog.prompt
(“Drag files here or”) - Text displayed just above the file select button, but only when the file list is empty.labels.delete
(“Delete File”) - Used as the aria-label
for the X icons which delete individual files.labels.[status]
- Used as the aria-label
on list items in the file list, depending on the file’s current status:labels.unloaded
(“Not Loaded”)labels.loading
(“Loading”)labels.loaded
(“Successful”)labels.error
(“Error Loading File”) - In addition to this label, the FileObject.errorMsg
is linked using aria-describedby
.The labels must all be strings because they’re used directly in aria-label
attributes, but buttonText
, prompt
, and FileObject.errorMsg
can all be React nodes (e.g. an I18nText
component from @repay/cactus-i18n
).
Name | Required | Default Value | Type | Description |
---|---|---|---|---|
accept | N | string[] | undefined | ||
buttonText | N | ReactNode | ||
labels | N | FileInfoLabels | undefined | ||
name | Y | string | ||
onBlur | N | FocusEventHandler<Target> | undefined | ||
onChange | N | ChangeEventHandler<Target> | undefined | ||
onFocus | N | FocusEventHandler<Target> | undefined | ||
prompt | N | ReactNode | ||
status | N | "success" | "warning" | "error" | null | undefined | ||
value | N | "" | FileObject[] | undefined |
Name | Required | Type | Description |
---|---|---|---|
m | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on top, left, bottom and right |
margin | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on top, left, bottom and right |
marginBottom | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on bottom |
marginLeft | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on left |
marginRight | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on right |
marginTop | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on top |
marginX | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on left and right |
marginY | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on top and bottom |
maxWidth | N | ResponsiveValue<MaxWidth<TLengthStyledSystem>, Required<Theme<TLengthStyledSystem>>> | undefined | The max-width CSS property sets the maximum width of an element. It prevents the used value of the width property from becoming larger than the value specified by max-width. [MDN reference](https://developer.mozilla.org/en-US/docs/Web/CSS/max-width) |
mb | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on bottom |
ml | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on left |
mr | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on right |
mt | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on top |
mx | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on left and right |
my | N | ResponsiveValue<string | number | symbol, Required<Theme<TLengthStyledSystem>>> | undefined | Margin on top and bottom |
width | N | ResponsiveValue<Width<TLengthStyledSystem>, Required<Theme<TLengthStyledSystem>>> | undefined | The width utility parses a component's `width` prop and converts it into a CSS width declaration. - Numbers from 0-1 are converted to percentage widths. - Numbers greater than 1 are converted to pixel values. - String values are passed as raw CSS values. - And arrays are converted to responsive width styles. |