Skip to content

Instantly share code, notes, and snippets.

@indiesquidge
Last active May 11, 2018 21:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save indiesquidge/0370f25d7b5c8436c8a0e6366d20f190 to your computer and use it in GitHub Desktop.
Save indiesquidge/0370f25d7b5c8436c8a0e6366d20f190 to your computer and use it in GitHub Desktop.

I've been using controlled components for near everything in my application as that has been the recommended way to go, and I subscribe to the idea that React should be in control of this state when possible.

However, I recently went through @ryanflorence's Advanced React course—which is a wonderful collection and breakdown of some powerful (and still uncommon) patterns available in React—and the last lecture is on controlled components.

At around 17:40 in the lecture video, Ryan does something very interesting. He writes some code that looks like this:

<Tabs
  defaultActiveIndex={this.state.currentTab}
  onChange={(index) => {
    this.setState({ currentTab: index })
  }}
/>

The context of this component is not necessarily relevant here (although it would help to understand by watching the lecture video; the lecture is free if you create an account); you can mentally replace the above snippet with a native input element if you'd like.

<input
  defaultValue={this.state.value}
  onChange={(e) => {
    this.setState({ value: e.target.value })
  }}
/>

⚠️ NOTE: if my above statement is incorrect, and form field elements are special cases compared to composite components when talking about controlled vs. uncontrolled components, please correct me.

The intention here is something I haven't seen or heard a lot of noise about. He is using a change handler with an uncontrolled component. Ryan then says this (emphasis mine):

We don't actually need to control [the active index]. Nothing else in the app is changing the active index; […] there are no buttons or anything that changes the currentTab. Even if I need to get the state in a change handler doesn't mean I have to control it. The only time you have to control a component is when the programmer, you, need to change the index, or the value, or whatever the controlled property is. We don't need to control it, we just want to know it here.

— Ryan Florence, Advanced React, Lecture 08 on "Controlled Components"

I've watched this video more than handful of times, and implemented a few controllable components myself, but only now have I realized I'm rather confused about how a pattern like this might be seen by the React community at large. Why do the React docs make no mention of this pattern for uncontrolled components? Why are there not any examples showing that it may be worth watching for changes without managing the state?

From what I read in the official docs, uncontrolled components are only ever used in conjunction with refs. However, what if all I want to do is know about the value? Is it an okay pattern to use defaultValue with a change handler for something like an input element if all I'm trying to do is read the value to affect something else in my app?

@indiesquidge
Copy link
Author

I asked Ryan himself about this during one of his workshops, and here is now my understanding of the question around "wh

onChange is used when you want to do something based on state, but you don't care about the state; you are want to make what was implicit state into explicit state.

defaultValue vs. value is for when you actually want to control the state; you want to manage the state yourself.

Notice that these have an asymmetrical dependency on each other.

For instance, you may which to provide a default value for a component, but after that you don't care about what it changes to, you only care to listen for changes.

<input defaultValue='hello' onChange={/* … */} />

In another instance, you may just want full control and management over the state. But note here that if you want to control and manage the state, by implication you are also saying that you want to make something explicit.

<input value='hello' onChange={/* … */} />

Thus, it is possible to provide a defaultValue by itself or with an onChange, but if you provide a value, you must provide an onChange—you cannot provide a controlled component a value only (you could, but then it's just static content).

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