Skip to content

Instantly share code, notes, and snippets.

@markerikson
Last active June 15, 2021 12:50
Show Gist options
  • Star 68 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save markerikson/d71cfc81687f11609d2559e8daee10cc to your computer and use it in GitHub Desktop.
Save markerikson/d71cfc81687f11609d2559e8daee10cc to your computer and use it in GitHub Desktop.
React "controlled" vs "uncontrolled" inputs explanation

[12:03 AM] acemarke: "controlled" and "uncontrolled" inputs
[12:04 AM] acemarke: if I have a plain, normal HTML page, and I put <input id="myTextbox" type="text" /> in my page(edited)
[12:04 AM] acemarke: and I start typing into that textbox
[12:04 AM] acemarke: it remembers what I've typed. The browser stores the current value for that input
[12:05 AM] acemarke: and then sometime later, I can get the actual element, say, const input = document.getElementById("myTextbox"), and I can ask it for its value: const currentText = input.value;
[12:05 AM] acemarke: good so far?
[12:08 AM] acemarke: I'll keep going, and let me know if you have questions
[12:08 AM] lozio: ok, actually I'm reading
[12:09 AM] lozio: good
[12:09 AM] acemarke: so, a normal HTML input field effectively stores its own value at all times, and you can get the element and ask for its value
[12:09 AM] acemarke: this is what we refer to as an "uncontrolled" input
[12:09 AM] acemarke: and is what you're probably used to with, say, jQuery or Backbone or similar
[12:10 AM] acemarke: I write my HTML form, I have a Submit button, when the button is clicked, I get each input and grab the value, put all the values in an object, and do something with that
[12:10 AM] acemarke: but, in React, the normal way we do things is different
[12:10 AM] acemarke: with React, normally every time we render we define what the current DOM output should be
[12:10 AM] acemarke: and indeed, in React, you can still create "uncontrolled" inputs:
[12:11 AM] lozio: use 'ref' right?
[12:11 AM] acemarke: yep, like:
[12:12 AM] acemarke:

const MyComponent extends Component {  
    onClick() {  
        const input = this.refs.myInput;  
        const value = input.value;  
        // do something with the value  
    }  
  
    render() {  
        return <input type="text" ref="myInput" />  
    }  
}  

[12:12 AM] acemarke: roughly
[12:13 AM] acemarke: but, there's a more React-standard way to do things
[12:13 AM] acemarke: with React, we can render an input, and tell it what its value should be: return <input type="text" value="Hello world!" />
[12:13 AM] acemarke: (this applies to all input types, btw, not just type="text")
[12:14 AM] acemarke: but, now we have a problem
[12:14 AM] acemarke: if I put my cursor in that input after the '!', and try to type "asdf".... nothing will happen
[12:14 AM] acemarke: because every time I type, React will re-render
[12:14 AM] acemarke: and React will see that the value is supposed to be "Hello world!"
[12:15 AM] lozio: Yes, That's my problem so far
[12:15 AM] acemarke: and when I type that first 'a' and the value becomes "Hello world!a", React will overwrite it
[12:15 AM] lozio: You explained my problem very well
[12:16 AM] acemarke: So, a "controlled" input is when you specify the value. And, in order to update the value, you must also specify an onChange callback for the input. The event in the callback will have the new suggested value from the input. It is then your job to take that new value and put it somewhere in your state, then re-render with the new value
[12:17 AM] acemarke:

class MyComponent extends Component {  
    constructor(props) {  
        super(props);  
          
        this.state = {  
            text : ""  
        };  
          
        this.onChange = this.onChange.bind(this);  
    }  
      
    onChange(e) {  
        const newText = e.target.value;  
        this.setState({text : newText});  
    }  
      
    render() {  
        return <input type="text" value={this.state.text} onChange={this.onChange} />  
    }  
}  

[12:18 AM] acemarke: so every time I type another character into that textbox, it fires "onChange", I take the new value, put it in the component's state, and re-render with the new value
[12:19 AM] acemarke: it may sound a bit more complicated. but, that means you never have trouble with your inputs being out of sync with your data
[12:19 AM] acemarke: because you always have the values in your state
[12:19 AM] acemarke: does that make sense?
[12:22 AM] lozio: What if I use Redux's store state value inject an input value, because Redux's state is not a React Component state
[12:23 AM] acemarke: same thing. doesn't matter whether you're putting the new value into Redux or React component state. It's the use of <input value={someValue} onChange={someOnChange} /> that makes it "controlled"
[12:24 AM] acemarke: that said, you might not want to go straight to Redux for every single character typed
[12:24 AM] acemarke: that could be a lot of actions
[12:24 AM] acemarke: and the data from Redux is coming back down to a React component anyway

@nithitvip
Copy link

[12:24 AM] acemarke: that said, you might not want to go straight to Redux for every single character typed
[12:24 AM] acemarke: that could be a lot of actions

for those lines above Did you mean that for the Form we just keep every on change value (like input name or address blah blah) into the local state and when we click submit button we will call action to redux?

@Lythom
Copy link

Lythom commented Nov 15, 2016

@might01 Sending a redux action for every input change can work smoothly for Inputs that have few changes (ie. checbox, radio, select). They can be wired directly to redux store.
For other inputs (text, date, number, etc.), storing the value in the component state can be better than triggering an action for perfs reasons (avoid transitional states to update uselessly other components) and debugging reasons (action log flooding). In this case, you can trigger redux action, indeed on a submit button click, but also onChange with a debounce, onBlur, or using any other detection method that indicate your user input task is over.

Case where you might want a submit button :

  • When there is an actual transaction to send to a server (it allows to send all data at once)
  • When there is an async action that needs to wait a return (you can explicit there is a response waiting occuring by disabling the button + spinner)
  • When the user might want to rollback changes (by not submitting)

Case where you might prefer a debounce :

  • When you want quick requests with the server or send data on the fly (ie. chat app, game)
  • When only the internal state is updated (ie. filters, visual preferences)

@carkod
Copy link

carkod commented Aug 30, 2017

Regarding

this.state = { text : "" };

If you are using

onChange(e) { this.setState({[e.target.name] : e.target.value}); }

Do you still have to write all the names in this.state?
What happens if you have a lot of forms, you may not want to write every single one in the initial state.

@ngohieutp
Copy link

ngohieutp commented Mar 28, 2018

I agree with @Carkok. There is a solution, that is to put || '' after each value in input.
return <input type="text" value={this.state.text || ''} onChange={this.onChange} />
I wonder if there's another better solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment