my blog https://overreacted.io
1--- 2title: How Does React Tell a Class from a Function? 3date: '2018-12-02' 4spoiler: We talk about classes, new, instanceof, prototype chains, and API design. 5cta: 'react' 6--- 7 8Consider this `Greeting` component which is defined as a function: 9 10```jsx 11function Greeting() { 12 return <p>Hello</p>; 13} 14``` 15 16React also supports defining it as a class: 17 18```jsx 19class Greeting extends React.Component { 20 render() { 21 return <p>Hello</p>; 22 } 23} 24``` 25 26(Until [recently](https://reactjs.org/docs/hooks-intro.html), that was the only way to use features like state.) 27 28When you want to render a `<Greeting />`, you don’t care how it’s defined: 29 30```jsx 31// Class or function — whatever. 32<Greeting /> 33``` 34 35But *React itself* cares about the difference! 36 37If `Greeting` is a function, React needs to call it: 38 39```jsx 40// Your code 41function Greeting() { 42 return <p>Hello</p>; 43} 44 45// Inside React 46const result = Greeting(props); // <p>Hello</p> 47``` 48 49But if `Greeting` is a class, React needs to instantiate it with the `new` operator and *then* call the `render` method on the just created instance: 50 51```jsx 52// Your code 53class Greeting extends React.Component { 54 render() { 55 return <p>Hello</p>; 56 } 57} 58 59// Inside React 60const instance = new Greeting(props); // Greeting {} 61const result = instance.render(); // <p>Hello</p> 62``` 63 64In both cases React’s goal is to get the rendered node (in this example, `<p>Hello</p>`). But the exact steps depend on how `Greeting` is defined. 65 66**So how does React know if something is a class or a function?** 67 68Just like in my [previous post](/why-do-we-write-super-props/), **you don’t *need* to know this to be productive in React.** I didn’t know this for years. Please don’t turn this into an interview question. In fact, this post is more about JavaScript than it is about React. 69 70This blog is for a curious reader who wants to know *why* React works in a certain way. Are you that person? Then let’s dig in together. 71 72**This is a long journey. Buckle up. This post doesn’t have much information about React itself, but we’ll go through some aspects of `new`, `this`, `class`, arrow functions, `prototype`, `__proto__`, `instanceof`, and how those things work together in JavaScript. Luckily, you don’t need to think about those as much when you *use* React. If you’re implementing React though...** 73 74(If you really just want to know the answer, scroll to the very end.) 75 76---- 77 78First, we need to understand why it’s important to treat functions and classes differently. Note how we use the `new` operator when calling a class: 79 80```jsx {5} 81// If Greeting is a function 82const result = Greeting(props); // <p>Hello</p> 83 84// If Greeting is a class 85const instance = new Greeting(props); // Greeting {} 86const result = instance.render(); // <p>Hello</p> 87``` 88 89Let’s get a rough sense of what the `new` operator does in JavaScript. 90 91--- 92 93In the old days, JavaScript did not have classes. However, you could express a similar pattern to classes using plain functions. **Concretely, you can use *any* function in a role similar to a class constructor by adding `new` before its call:** 94 95```jsx 96// Just a function 97function Person(name) { 98 this.name = name; 99} 100 101var fred = new Person('Fred'); // ✅ Person {name: 'Fred'} 102var george = Person('George'); // 🔴 Won’t work 103``` 104 105You can still write code like this today! Try it in DevTools. 106 107If you called `Person('Fred')` **without** `new`, `this` inside it would point to something global and useless (for example, `window` or `undefined`). So our code would crash or do something silly like setting `window.name`. 108 109By adding `new` before the call, we say: “Hey JavaScript, I know `Person` is just a function but let’s pretend it’s something like a class constructor. **Create an `{}` object and point `this` inside the `Person` function to that object so I can assign stuff like `this.name`. Then give that object back to me.**” 110 111That’s what the `new` operator does. 112 113```jsx 114var fred = new Person('Fred'); // Same object as `this` inside `Person` 115``` 116 117The `new` operator also makes anything we put on `Person.prototype` available on the `fred` object: 118 119```jsx {4-6,9} 120function Person(name) { 121 this.name = name; 122} 123Person.prototype.sayHi = function() { 124 alert('Hi, I am ' + this.name); 125} 126 127var fred = new Person('Fred'); 128fred.sayHi(); 129``` 130 131This is how people emulated classes before JavaScript added them directly. 132 133--- 134 135So `new` has been around in JavaScript for a while. However, classes are more recent. They let us rewrite the code above to match our intent more closely: 136 137```jsx 138class Person { 139 constructor(name) { 140 this.name = name; 141 } 142 sayHi() { 143 alert('Hi, I am ' + this.name); 144 } 145} 146 147let fred = new Person('Fred'); 148fred.sayHi(); 149``` 150 151*Capturing developer’s intent* is important in language and API design. 152 153If you write a function, JavaScript can’t guess if it’s meant to be called like `alert()` or if it serves as a constructor like `new Person()`. Forgetting to specify `new` for a function like `Person` would lead to confusing behavior. 154 155**Class syntax lets us say: “This isn’t just a function — it’s a class and it has a constructor”.** If you forget `new` when calling it, JavaScript will raise an error: 156 157```jsx 158let fred = new Person('Fred'); 159// ✅ If Person is a function: works fine 160// ✅ If Person is a class: works fine too 161 162let george = Person('George'); // We forgot `new` 163// 😳 If Person is a constructor-like function: confusing behavior 164// 🔴 If Person is a class: fails immediately 165``` 166 167This helps us catch mistakes early instead of waiting for some obscure bug like `this.name` being treated as `window.name` instead of `george.name`. 168 169However, it means that React needs to put `new` before calling any class. It can’t just call it as a regular function, as JavaScript would treat it as an error! 170 171```jsx 172class Counter extends React.Component { 173 render() { 174 return <p>Hello</p>; 175 } 176} 177 178// 🔴 React can't just do this: 179const instance = Counter(props); 180``` 181 182This spells trouble. 183 184--- 185 186Before we see how React solves this, it’s important to remember most people using React use compilers like Babel to compile away modern features like classes for older browsers. So we need to consider compilers in our design. 187 188In early versions of Babel, classes could be called without `new`. However, this was fixed — by generating some extra code: 189 190```jsx 191function Person(name) { 192 // A bit simplified from Babel output: 193 if (!(this instanceof Person)) { 194 throw new TypeError("Cannot call a class as a function"); 195 } 196 // Our code: 197 this.name = name; 198} 199 200new Person('Fred'); // ✅ Okay 201Person('George'); // 🔴 Cannot call a class as a function 202``` 203 204You might have seen code like this in your bundle. That’s what all those `_classCallCheck` functions do. (You can reduce the bundle size by opting into the “loose mode” with no checks but this might complicate your eventual transition to real native classes.) 205 206--- 207 208By now, you should roughly understand the difference between calling something with `new` or without `new`: 209 210| | `new Person()` | `Person()` | 211|---|---|---| 212| `class` | ✅ `this` is a `Person` instance | 🔴 `TypeError` 213| `function` | ✅ `this` is a `Person` instance | 😳 `this` is `window` or `undefined` | 214 215This is why it’s important for React to call your component correctly. **If your component is defined as a class, React needs to use `new` when calling it.** 216 217So can React just check if something is a class or not? 218 219Not so easy! Even if we could [tell a class from a function in JavaScript](https://stackoverflow.com/questions/29093396/how-do-you-check-the-difference-between-an-ecmascript-6-class-and-function), this still wouldn’t work for classes processed by tools like Babel. To the browser, they’re just plain functions. Tough luck for React. 220 221--- 222 223Okay, so maybe React could just use `new` on every call? Unfortunately, that doesn’t always work either. 224 225With regular functions, calling them with `new` would give them an object instance as `this`. It’s desirable for functions written as constructor (like our `Person` above), but it would be confusing for function components: 226 227```jsx 228function Greeting() { 229 // We wouldn’t expect `this` to be any kind of instance here 230 return <p>Hello</p>; 231} 232``` 233 234That could be tolerable though. There are two *other* reasons that kill this idea. 235 236--- 237 238The first reason why always using `new` wouldn’t work is that for native arrow functions (not the ones compiled by Babel), calling with `new` throws an error: 239 240```jsx 241const Greeting = () => <p>Hello</p>; 242new Greeting(); // 🔴 Greeting is not a constructor 243``` 244 245This behavior is intentional and follows from the design of arrow functions. One of the main perks of arrow functions is that they *don’t* have their own `this` value — instead, `this` is resolved from the closest regular function: 246 247```jsx {2,6,7} 248class Friends extends React.Component { 249 render() { 250 const friends = this.props.friends; 251 return friends.map(friend => 252 <Friend 253 // `this` is resolved from the `render` method 254 size={this.props.size} 255 name={friend.name} 256 key={friend.id} 257 /> 258 ); 259 } 260} 261``` 262 263Okay, so **arrow functions don’t have their own `this`.** But that means they would be entirely useless as constructors! 264 265```jsx 266const Person = (name) => { 267 // 🔴 This wouldn’t make sense! 268 this.name = name; 269} 270``` 271 272Therefore, **JavaScript disallows calling an arrow function with `new`.** If you do it, you probably made a mistake anyway, and it’s best to tell you early. This is similar to how JavaScript doesn’t let you call a class *without* `new`. 273 274This is nice but it also foils our plan. React can’t just call `new` on everything because it would break arrow functions! We could try detecting arrow functions specifically by their lack of `prototype`, and not `new` just them: 275 276```jsx 277(() => {}).prototype // undefined 278(function() {}).prototype // {constructor: f} 279``` 280 281But this [wouldn’t work](https://github.com/facebook/react/issues/4599#issuecomment-136562930) for functions compiled with Babel. This might not be a big deal, but there is another reason that makes this approach a dead end. 282 283--- 284 285Another reason we can’t always use `new` is that it would preclude React from supporting components that return strings or other primitive types. 286 287```jsx 288function Greeting() { 289 return 'Hello'; 290} 291 292Greeting(); // ✅ 'Hello' 293new Greeting(); // 😳 Greeting {} 294``` 295 296This, again, has to do with the quirks of the [`new` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) design. As we saw earlier, `new` tells the JavaScript engine to create an object, make that object `this` inside the function, and later give us that object as a result of `new`. 297 298However, JavaScript also allows a function called with `new` to *override* the return value of `new` by returning some other object. Presumably, this was considered useful for patterns like pooling where we want to reuse instances: 299 300```jsx {1-2,7-8,17-18} 301// Created lazily 302var zeroVector = null; 303 304function Vector(x, y) { 305 if (x === 0 && y === 0) { 306 if (zeroVector !== null) { 307 // Reuse the same instance 308 return zeroVector; 309 } 310 zeroVector = this; 311 } 312 this.x = x; 313 this.y = y; 314} 315 316var a = new Vector(1, 1); 317var b = new Vector(0, 0); 318var c = new Vector(0, 0); // 😲 b === c 319``` 320 321However, `new` also *completely ignores* a function’s return value if it’s *not* an object. If you return a string or a number, it’s like there was no `return` at all. 322 323```jsx 324function Answer() { 325 return 42; 326} 327 328Answer(); // ✅ 42 329new Answer(); // 😳 Answer {} 330``` 331 332There is just no way to read a primitive return value (like a number or a string) from a function when calling it with `new`. So if React always used `new`, it would be unable to add support components that return strings! 333 334That’s unacceptable so we need to compromise. 335 336--- 337 338What did we learn so far? React needs to call classes (including Babel output) *with* `new` but it needs to call regular functions or arrow functions (including Babel output) *without* `new`. And there is no reliable way to distinguish them. 339 340**If we can’t solve a general problem, can we solve a more specific one?** 341 342When you define a component as a class, you’ll likely want to extend `React.Component` for built-in methods like `this.setState()`. **Rather than try to detect all classes, can we detect only `React.Component` descendants?** 343 344Spoiler: this is exactly what React does. 345 346--- 347 348Perhaps, the idiomatic way to check if `Greeting` is a React component class is by testing if `Greeting.prototype instanceof React.Component`: 349 350```jsx 351class A {} 352class B extends A {} 353 354console.log(B.prototype instanceof A); // true 355``` 356 357I know what you’re thinking. What just happened here?! To answer this, we need to understand JavaScript prototypes. 358 359You might be familiar with the “prototype chain”. Every object in JavaScript might have a “prototype”. When we write `fred.sayHi()` but `fred` object has no `sayHi` property, we look for `sayHi` property on `fred`’s prototype. If we don’t find it there, we look at the next prototype in the chain — `fred`’s prototype’s prototype. And so on. 360 361**Confusingly, the `prototype` property of a class or a function _does not_ point to the prototype of that value.** I’m not kidding. 362 363```jsx 364function Person() {} 365 366console.log(Person.prototype); // 🤪 Not Person's prototype 367console.log(Person.__proto__); // 😳 Person's prototype 368``` 369 370So the “prototype chain” is more like `__proto__.__proto__.__proto__` than `prototype.prototype.prototype`. This took me years to get. 371 372What’s the `prototype` property on a function or a class, then? **It’s the `__proto__` given to all objects `new`ed with that class or a function!** 373 374```jsx {8} 375function Person(name) { 376 this.name = name; 377} 378Person.prototype.sayHi = function() { 379 alert('Hi, I am ' + this.name); 380} 381 382var fred = new Person('Fred'); // Sets `fred.__proto__` to `Person.prototype` 383``` 384 385And that `__proto__` chain is how JavaScript looks up properties: 386 387```jsx 388fred.sayHi(); 389// 1. Does fred have a sayHi property? No. 390// 2. Does fred.__proto__ have a sayHi property? Yes. Call it! 391 392fred.toString(); 393// 1. Does fred have a toString property? No. 394// 2. Does fred.__proto__ have a toString property? No. 395// 3. Does fred.__proto__.__proto__ have a toString property? Yes. Call it! 396``` 397 398In practice, you should almost never need to touch `__proto__` from the code directly unless you’re debugging something related to the prototype chain. If you want to make stuff available on `fred.__proto__`, you’re supposed to put it on `Person.prototype`. At least that’s how it was originally designed. 399 400The `__proto__` property wasn’t even supposed to be exposed by browsers at first because the prototype chain was considered an internal concept. But some browsers added `__proto__` and eventually it was begrudgingly standardized (but deprecated in favor of `Object.getPrototypeOf()`). 401 402**And yet I still find it very confusing that a property called `prototype` does not give you a value’s prototype** (for example, `fred.prototype` is undefined because `fred` is not a function). Personally, I think this is the biggest reason even experienced developers tend to misunderstand JavaScript prototypes. 403 404--- 405 406This is a long post, eh? I’d say we’re 80% there. Hang on. 407 408We know that when we say `obj.foo`, JavaScript actually looks for `foo` in `obj`, `obj.__proto__`, `obj.__proto__.__proto__`, and so on. 409 410With classes, you’re not exposed directly to this mechanism, but `extends` also works on top of the good old prototype chain. That’s how our React class instance gets access to methods like `setState`: 411 412```jsx {1,9,13} 413class Greeting extends React.Component { 414 render() { 415 return <p>Hello</p>; 416 } 417} 418 419let c = new Greeting(); 420console.log(c.__proto__); // Greeting.prototype 421console.log(c.__proto__.__proto__); // React.Component.prototype 422console.log(c.__proto__.__proto__.__proto__); // Object.prototype 423 424c.render(); // Found on c.__proto__ (Greeting.prototype) 425c.setState(); // Found on c.__proto__.__proto__ (React.Component.prototype) 426c.toString(); // Found on c.__proto__.__proto__.__proto__ (Object.prototype) 427``` 428 429In other words, **when you use classes, an instance’s `__proto__` chain “mirrors” the class hierarchy:** 430 431```jsx 432// `extends` chain 433Greeting 434 → React.Component 435 → Object (implicitly) 436 437// `__proto__` chain 438new Greeting() 439 → Greeting.prototype 440 → React.Component.prototype 441 → Object.prototype 442``` 443 4442 Chainz. 445 446--- 447 448Since the `__proto__` chain mirrors the class hierarchy, we can check whether a `Greeting` extends `React.Component` by starting with `Greeting.prototype`, and then following down its `__proto__` chain: 449 450```jsx {3,4} 451// `__proto__` chain 452new Greeting() 453 → Greeting.prototype // 🕵️ We start here 454 → React.Component.prototype // ✅ Found it! 455 → Object.prototype 456``` 457 458Conveniently, `x instanceof Y` does exactly this kind of search. It follows the `x.__proto__` chain looking for `Y.prototype` there. 459 460Normally, it’s used to determine whether something is an instance of a class: 461 462```jsx 463let greeting = new Greeting(); 464 465console.log(greeting instanceof Greeting); // true 466// greeting (🕵️‍ We start here) 467// .__proto__ → Greeting.prototype (✅ Found it!) 468// .__proto__ → React.Component.prototype 469// .__proto__ → Object.prototype 470 471console.log(greeting instanceof React.Component); // true 472// greeting (🕵️‍ We start here) 473// .__proto__ → Greeting.prototype 474// .__proto__ → React.Component.prototype (✅ Found it!) 475// .__proto__ → Object.prototype 476 477console.log(greeting instanceof Object); // true 478// greeting (🕵️‍ We start here) 479// .__proto__ → Greeting.prototype 480// .__proto__ → React.Component.prototype 481// .__proto__ → Object.prototype (✅ Found it!) 482 483console.log(greeting instanceof Banana); // false 484// greeting (🕵️‍ We start here) 485// .__proto__ → Greeting.prototype 486// .__proto__ → React.Component.prototype 487// .__proto__ → Object.prototype (🙅‍ Did not find it!) 488``` 489 490But it would work just as fine to determine if a class extends another class: 491 492```jsx 493console.log(Greeting.prototype instanceof React.Component); 494// greeting 495// .__proto__ → Greeting.prototype (🕵️‍ We start here) 496// .__proto__ → React.Component.prototype (✅ Found it!) 497// .__proto__ → Object.prototype 498``` 499 500And that check is how we could determine if something is a React component class or a regular function. 501 502--- 503 504That’s not what React does though. 😳 505 506One caveat to the `instanceof` solution is that it doesn’t work when there are multiple copies of React on the page, and the component we’re checking inherits from *another* React copy’s `React.Component`. Mixing multiple copies of React in a single project is bad for several reasons but historically we’ve tried to avoid issues when possible. (With Hooks, we [might need to](https://github.com/facebook/react/issues/13991) force deduplication though.) 507 508One other possible heuristic could be to check for presence of a `render` method on the prototype. However, at the time it [wasn’t clear](https://github.com/facebook/react/issues/4599#issuecomment-129714112) how the component API would evolve. Every check has a cost so we wouldn’t want to add more than one. This would also not work if `render` was defined as an instance method, such as with the class property syntax. 509 510So instead, React [added](https://github.com/facebook/react/pull/4663) a special flag to the base component. React checks for the presence of that flag, and that’s how it knows whether something is a React component class or not. 511 512Originally the flag was on the base `React.Component` class itself: 513 514```jsx 515// Inside React 516class Component {} 517Component.isReactClass = {}; 518 519// We can check it like this 520class Greeting extends Component {} 521console.log(Greeting.isReactClass); // ✅ Yes 522``` 523 524However, some class implementations we wanted to target [did not](https://github.com/scala-js/scala-js/issues/1900) copy static properties (or set the non-standard `__proto__`), so the flag was getting lost. 525 526This is why React [moved](https://github.com/facebook/react/pull/5021) this flag to `React.Component.prototype`: 527 528```jsx 529// Inside React 530class Component {} 531Component.prototype.isReactComponent = {}; 532 533// We can check it like this 534class Greeting extends Component {} 535console.log(Greeting.prototype.isReactComponent); // ✅ Yes 536``` 537 538**And this is literally all there is to it.** 539 540You might be wondering why it’s an object and not just a boolean. It doesn’t matter much in practice but early versions of Jest (before Jest was Good™️) had automocking turned on by default. The generated mocks omitted primitive properties, [breaking the check](https://github.com/facebook/react/pull/4663#issuecomment-136533373). Thanks, Jest. 541 542The `isReactComponent` check is [used in React](https://github.com/facebook/react/blob/769b1f270e1251d9dbdce0fcbd9e92e502d059b8/packages/react-reconciler/src/ReactFiber.js#L297-L300) to this day. 543 544If you don’t extend `React.Component`, React won’t find `isReactComponent` on the prototype, and won’t treat component as a class. Now you know why [the most upvoted answer](https://stackoverflow.com/a/42680526/458193) for `Cannot call a class as a function` error is to add `extends React.Component`. Finally, a [warning was added](https://github.com/facebook/react/pull/11168) that warns when `prototype.render` exists but `prototype.isReactComponent` doesn’t. 545 546--- 547 548You might say this story is a bit of a bait-and-switch. **The actual solution is really simple, but I went on a huge tangent to explain *why* React ended up with this solution, and what the alternatives were.** 549 550In my experience, that’s often the case with library APIs. For an API to be simple to use, you often need to consider the language semantics (possibly, for several languages, including future directions), runtime performance, ergonomics with and without compile-time steps, the state of the ecosystem and packaging solutions, early warnings, and many other things. The end result might not always be the most elegant, but it must be practical. 551 552**If the final API is successful, _its users_ never have to think about this process.** Instead they can focus on creating apps. 553 554But if you’re also curious... it’s nice to know how it works.