23

In my react component I have a file input:

<input type="file" onChange={this.onFileChange.bind(this)} />` 

and my onFileChange is:

onFileChange(e) {
  let file = e.target.files[0];
  this.setState(() => ({ file: e.target.files[0] })); //doesnt work
  // this.setState(() => ({ file })); //works
  // this.setState({ file: e.target.files[0] }); //works
}

This first way of setting the states fails with an error:

Cannot read property 'files' of null

React also gives the following warning:

This synthetic event is reused for performance reasons. If you're 
seeing this, you're accessing the property 'target' on a 
released/nullified synthetic event

But the last two ways of setting the state give no errors or warnings. Why is this happening?

3
  • Sorry im missing the point. Whats wrong with this.setState(() => ({ file })); that is the correct way to set state. I would also change let to const Commented Nov 22, 2017 at 19:57
  • @Zinc I was just curious why the first method didn't work! And I learned something new thanks to the responses :) Commented Nov 23, 2017 at 3:00
  • Declaring a variable let file = e.target.files[0]; (line 2), you are creating the variable in a closure of an asynchronous function () => ({ file }) (line 4). That is why it work. Note that e is just an argument of onFileChange and it is not accessible in that asynchronous function (that is why () => ({ file: e.target.files[0] }) does not work) Commented Oct 8, 2019 at 13:12

5 Answers 5

37

The setState function is executed in asynchronous context.

By the time the state is updated the e.target reference might or might not be gone.

const file = e.target.files[0]; can be used to "remember" the value as in your example.

Sign up to request clarification or add additional context in comments.

2 Comments

Ahhh! Thanks. I didn't realize setState was asynchronous
The file variable is declared in the closure of a declaration of the () => ({ file }) function that is why file is accessible by it later on.
4

What is the reason for calling setState with callback ? this.setState({ file: e.target.files[0] }) should do the job.

In your code you are referring to a synthetic event object which no longer holds information about the original DOM event. React reuses the event objects for performance reasons.

Alternatively you can use:

let file = e.target.files[0]; const files = e.target.files this.setState(() => ({ file: files[0] })); //doesnt work

2 Comments

so this.setState(() => {}) with an arrow function is asynchronous while this.setState({}) is not? From the source I learned react form, I was told the former method is the standard. I didn't know it was async
I used the term unfortunately. All state updates in React are asynchronous. What I meant by 'async' was actually passing updater callback instead of constructed object to setState. I think it is just an API difference and I find setState({}) to be more readable. The updater callback must be used when you calculate new state based on the actual state (like incrementing). Reason why setState(updater) is mentioned in docs as first maybe has to do with forcing people think it is an async operation which many tended to forgot (or did not know in first place).
3

React uses event pooling, you can read more about it in the docs here https://reactjs.org/docs/events.html

setState is an asynchronous function

this.setState(() => ({ file })); // is correct

Comments

2

Very simple/basic example to do the same task:

class Hello extends React.Component {
    constructor(props) {
    super(props);
    this.state = {
    file: ''
    };
  }

  render() {
    return <div>
    <input type='file' onChange={(e) => {
    this.setState({file: e.target.files[0]}, () => {
        console.log('state', this.state);
    })
    }} />
    </div>;
  }
}

ReactDOM.render(
  <Hello name="World" />,
  document.getElementById('container')
);

I have added console log when the state will be set it will log the file details. You can see in the log when you select a file the state includes the file data.

To see the console log you need to right click and inspect and see the Console.

Checkout working example here https://jsfiddle.net/1oj3h417/2/

Let me know if you have any doubt

2 Comments

Thanks. I have a working version as you can see in the other two set state methods that are commented out. I posted because I'm curious why the first way doesn't work
its because react re-uses its event
0

class Example extends React.Component {

  onFileChange = e => {
    let file = e.target.files[0];
    this.setState(() => ({ file: file }));
  }
  render() {
    return <input type="file" onChange={this.onFileChange} />;
  }
}

ReactDOM.render(
  <Example />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="root">
</div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.