forked from
danabra.mov/overreacted
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.