Home > Enterprise >  The second time onChange is triggered, custom component does not update
The second time onChange is triggered, custom component does not update

Time:10-23

Question: When props.files is console logged (look at code below to see where it's logged), it works the first time. But, when I select a new set of files, it never console logs anything again, unless I reload the page and start over. What am I doing wrong here?

Info on my code:

I am using React Bootstrap Form Components (scroll to "File Input") for the following custom component (i.e. Form.Group, Form.Label, Form.Control). Code is heavily simplified for clarity:

const UploadFile = (props) => {
  // stuff here

  console.log(props.files);

  const filesFunction = (filesInput) {
     // do stuff with filesInput
  }
  
  return {
     <Form.Group controlId="formFileMultiple" className="mb-3">
        <Form.Label> Files </Form.Label>
        <Form.Control 
           type="file" multiple 
           onChange={props.handleFilesChange}
           onChange={filesFunction(files)}
           name="files"
           key="files"
        />
     </Form.Group>
  }

In the parent, I have an instance of the custom UploadFile component, as such:

const [listOfFiles, setListOfFiles] = useState([]);

const handleFilesChange = (e) {
   setListOfFiles(e.target.files);
}

<UploadFile 
   handleFilesChange = { e => handleFilesChange(e) }
   files = {listOfFiles}
/>

CodePudding user response:

EDIT: If you want to console.log to be triggered in the UploadFile component, you have to trigger a rerender. Currently I don't see you using props.files inside the render method of the UploadFile component. Maybe try this:

const UploadFile = (props) => {
  // stuff here

  console.log(props.files);
  const randomClassName = `random-class-name-${props.files.length}`;

  return {
     <Form.Group controlId="formFileMultiple" className="mb-3">
        <Form.Label className={randomClassName}> Files </Form.Label>
        <Form.Control 
           type="file" multiple 
           onChange={props.handleFilesChange}
           name="files"
           key="files"
        />
     </Form.Group>
  }
}

But the proper way of doing this would be using the useEffect hook, which gets triggered every time your variable changes independently of the render method. Here's an example:

import { useEffect } from "react";

const UploadFile = (props) => {
  
  useEffect(() => {
    // stuff here
    console.log(props.files);
  }, [props.files]);
  
  return {
     <Form.Group controlId="formFileMultiple" className="mb-3">
        <Form.Label> Files </Form.Label>
        <Form.Control 
           type="file" multiple 
           onChange={props.handleFilesChange}
           name="files"
           key="files"
        />
     </Form.Group>
  }
}

CodePudding user response:

React doesn't know how to manage the FileList and doesn't see these props changes in the child component. If you convert FileList to array it works perfectly.

const UploadFile = (props) => {
  // stuff here

  console.log(props.files);
  
  return <ReactBootstrap.Form.Group controlId="formFileMultiple" className="mb-3">
      <ReactBootstrap.Form.Label> Files </ReactBootstrap.Form.Label>
      <ReactBootstrap.Form.Control 
         type="file" multiple 
         onChange={props.handleFilesChange}
         name="files"
         key="files"
      />
   </ReactBootstrap.Form.Group>
}

function App() {
  const [listOfFiles, setListOfFiles] = React.useState([]);

  const handleFilesChange = (e) => {
     setListOfFiles(Array.from(e.target.files));
  }

  return <UploadFile 
     handleFilesChange = { e => handleFilesChange(e) }
     files = {listOfFiles}
  />
}

ReactDOM.render(
  <App />, 
  document.getElementById('root')
)
<script src="https://unpkg.com/react/umd/react.production.min.js" crossorigin></script>

<script
  src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"
  crossorigin></script>

<script
  src="https://unpkg.com/react-bootstrap@next/dist/react-bootstrap.min.js"
  crossorigin></script>

<div id="root"></div>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Clarification:

The FileList is unmanaged by react, because its value can only be set by a user and not programmatically: https://reactjs.org/docs/uncontrolled-components.html#the-file-input-tag

  • Related