Search code examples
reactjsreact-hook-formref

Reset File Input Refs Across Components to Allow Re-uploading


I only came across very hacky solutions, so maybe someone has a better idea how to solve it.

Here's what I'm trying to do:

  • The UploadComponent lets the user select files using refs.
  • Once a file is selected, it’s passed to the parent component (App), which keeps track of the uploaded files.
  • The FilePreviewComponent displays the uploaded files and allows the user to delete a file. When a file is deleted, I want to reset the file input refs in the UploadComponent so that the same file can be uploaded again.

For now I couldn't find any solution to reset refs cross-component. Here is code example:

 const App = () => {
  const { control, handleSubmit, setValue, getValues } = useForm();
  const [selectedFiles, setSelectedFiles] = useState([]);

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* Upload Component */}
      <Controller
        name="files"
        control={control}
        render={({ field: { value, onChange } }) => (
          <UploadComponent
            onFileSelected={(files) => {
              const updatedFiles = [...value, ...files];
              onChange(updatedFiles);
              setSelectedFiles(updatedFiles);
            }}
          />
        )}
      />

      {/* File Preview Component */}
      <Controller
        name="files"
        control={control}
        render={({ field: { value } }) => (
          <FilePreviewComponent
            files={value}
            onDelete={(fileToDelete) => {
              const updatedFiles = value.filter((file) => file.uri !== fileToDelete.uri);
              setSelectedFiles(updatedFiles);
              setValue("files", updatedFiles);
            }}
          />
        )}
      />

      <button type="submit">Submit</button>
    </form>
  );
};

export default App;

And here is the upload component.

const UploadComponent = ({ onFileSelected }) => {
 
  const fileInputRef = useRef(null);


  const handleFileUpload = (e) => {
    const files = Array.from(e.target.files).map((file) => ({
      uri: URL.createObjectURL(file), // Create a temporary URL for preview
      name: file.name,
      type: file.type,
      blob: file, 
    }));
    onFileSelected(files); 
  };

  return (
    <div>
      {/* Button to trigger file input */}
      <button onClick={() => fileInputRef.current.click()}>Upload File</button>

      {/* Hidden file input triggered by button */}
      <input
        ref={fileInputRef}
        type="file"
        accept="image/*,application/pdf" 
        style={{ display: "none" }} 
        onChange={handleFileUpload} 
      />
    </div>
  );
};

export default UploadComponent;

Solution

  • The issue reported here may not be related with ref. It may be an issue related with input element itself.

    What is meant here is, a retry of a file upload also suffers from the same issue as it is not allowed. Furthermore, when a file has been uploaded, but before confirming the form, the same file needs to be uploaded again - due to some reason. The below html copied from the post may not support this use case.

    <input
     ref={fileInputRef}
     type="file"
     accept="image/*,application/pdf" 
     style={{ display: "none" }} 
     onChange={handleFileUpload} 
    />
    

    However the same use case may be possible when the value property is also used. It can also be done by manipulating the event object as well.

    Please see below a sample code below, showing the same.

    App.js

    export default function App() {
      return (
        <>
          <label>Retry a same file upload : Disallowed</label>
          <input type="file" onChange={(e) => console.log(e.target.value)}></input>
          <br></br>
          <label>Retry a same file upload : allowed method 1</label>
          <input
            type="file"
            value=""
            onChange={(e) => console.log(e.target.value)}
          ></input>
          <br></br>
          <label>Retry a same file upload : allowed method 2</label>
          <input
            type="file"
            onChange={(e) => {
              console.log(e.target.value);
              e.target.value = '';
            }}
          ></input>
          <br></br>
        </>
      );
    }
    

    Test plan

    After the app has been loaded, each of three upload buttons will be clicked twice to upload a same file.

    Test results

    The console logs generated

    // C:\somefile.txt - logged only once
    // C:\somefile.txt - logged twice 
    // C:\somefile.txt - logged twice
    

    Observation

    The logs show that, the first input declaration does not allow retry. However, the next two input declarations do support that. Of which, the first one is implemented by value property, and the second one is by manipulating value property through scripting.

    Solution proposal

    Perhaps after applying one of the options discussed above, the reported issue may be resolved. The reason for this proposal is, as we know, ref is just an access to a DOM element, it does not have anything of its own. Therefore if the DOM element is capable of doing something, then the ref to it will also be reflecting the same.

    Citation:

    Can't upload same file twice