Codebase Overview

This section will give you an overview of the React codebase organization, its conventions, and the implementation.

If you want to contribute to React we hope that this guide will help you feel more comfortable making changes.

We don't necessarily recommend any of these conventions in React apps. Many of them exist for historical reasons and might change with time.

Custom Module System #

At Facebook, internally we use a custom module system called "Haste". It is similar to CommonJS and also uses require() but has a few important differences that often confuse outside contributors.

In CommonJS, when you import a module, you need to specify its relative path:

// Importing from the same folder:
var setInnerHTML = require('./setInnerHTML');

// Importing from a different folder:
var setInnerHTML = require('../utils/setInnerHTML');

// Importing from a deeply nested folder:
var setInnerHTML = require('../client/utils/setInnerHTML');

However, with Haste all filenames are globally unique. In the React codebase, you can import any module from any other module by its name alone:

var setInnerHTML = require('setInnerHTML');

Haste was originally developed for giant apps like Facebook. It's easy to move files to different folders and import them without worrying about relative paths. The fuzzy file search in any editor always takes you to the correct place thanks to globally unique names.

React itself was extracted from Facebook codebase and uses Haste for historical reasons. In the future, we will probably migrate React to use CommonJS or ES Modules to be more aligned with the community. However this requires changes in Facebook internal infrastructure so it is unlikely to happen very soon.

Haste will make more sense to you if you remember a few rules:

  • All filenames in the React source code are unique. This is why they're sometimes verbose.
  • When you add a new file, make sure you include a license header. You can copy it from any existing file. A license header always includes a line like this. Change it to match the name of the file you created.
  • Don’t use relative paths when importing. Instead of require('./setInnerHTML'), write require('setInnerHTML').

When we compile React for npm, a script copies all the modules into a single flat directory called lib and prepends all require() paths with ./. This way Node, Browserify, Webpack, and other tools can understand React build output without being aware of Haste.

If you're reading React source on GitHub and want to jump to a file, press "t".

This is a GitHub shortcut for searching the current repo for fuzzy filename matches. Start typing the name of the file you are looking for, and it will show up as the first match.

External Dependencies #

React has almost no external dependencies. Usually a require() points to a file in React's own codebase. However there are a few relatively rare exceptions.

If you see a require() that does not correspond to a file in the React repository, you can look in a special repository called fbjs. For example, require('warning') will resolve to the warning module from fbjs.

The fbjs repository exists because React shares some small utilities with libraries like Relay, and we keep them in sync. We don't depend on equivalent small modules in the Node ecosystem because we want Facebook engineers to be able to make changes to them whenever necessary. None of the utilities inside fbjs are considered to be public API, and they are only intended for use by Facebook projects such as React.

Top-Level Folders #

After cloning the React repository, you will see a few top-level folders in it:

  • src is the source code of React. If your change is related to the code, src is where you'll spend most of your time.
  • docs is the React documentation website. When you change APIs, make sure to update the relevant Markdown files.
  • examples contains a few small React demos with different build setups.
  • packages contains metadata (such as package.json) for all packages in the React repository. Nevertheless their source code is still located inside src.
  • build is the build output of React. It is not in the repository but it will appear in your React clone after you build it for the first time.

There are a few other top-level folders but they are mostly used for the tooling and you likely won't ever encounter them when contributing.

Colocated Tests #

We don't have a top-level directory for unit tests. Instead, we put them into a directory called __tests__ relative to the files that they test.

For example, a test for setInnerHTML.js is located in __tests__/setInnerHTML-test.js right next to it.

Shared Code #

Even though Haste allows us to import any module from anywhere in the repository, we follow a convention to avoid cyclic dependencies and other unpleasant surprises. By convention, a file may only import files in the same folder or in subfolders below.

For example, files inside src/renderers/dom/client may import other files in the same folder or any folder below.

However they can't import modules from src/renderers/dom/server because it is not a child directory of src/renderers/dom/client.

There is an exception to this rule. Sometimes we do need to share functionality between two groups of modules. In this case we hoist the shared module up to a folder called shared inside the closest common ancestor folder of the modules that need to rely on it.

For example, code shared between src/renderers/dom/client and src/renderers/dom/server lives in src/renderers/dom/shared.

By the same logic, if src/renderers/dom/client needs to share a utility with something in src/renderers/native, this utility would be in src/renderers/shared.

This convention is not enforced but we check for it during a pull request review.

Warnings and Invariants #

The React codebase uses the warning module to display warnings:

var warning = require('warning');

warning(
  2 + 2 === 4,
  'Math is not working today.'
);

The warning is shown when the warning condition is false.

One way to think about it is that the condition should reflect the normal situation rather than the exceptional one.

It is a good idea to avoid spamming the console with duplicate warnings:

var warning = require('warning');

var didWarnAboutMath = false;
if (!didWarnAboutMath) {
  warning(
    2 + 2 === 4,
    'Math is not working today.'
  );
  didWarnAboutMath = true;
}

Warnings are only enabled in development. In production, they are completely stripped out. If you need to forbid some code path from executing, use invariant module instead:

var invariant = require('invariant');

invariant(
  2 + 2 === 4,
  'You shall not pass!'
);

The invariant is thrown when the invariant condition is false.

"Invariant" is just a way of saying "this condition always holds true". You can think about it as making an assertion.

It is important to keep development and production behavior similar, so invariant throws both in development and in production. The error messages are automatically replaced with error codes in production to avoid negatively affecting the byte size.

Development and Production #

You can use __DEV__ pseudo-global variable in the codebase to guard development-only blocks of code.

It is inlined during the compile step, and turns into process.env.NODE_ENV !== 'production' checks in the CommonJS builds.

For standalone builds, it becomes true in the unminified build, and gets completely stripped out with the if blocks it guards in the minified build.

if (__DEV__) {
  // This code will only run in development.
}

 JSDoc #

Some of the internal and public methods are annotated with JSDoc annotations:

/**
  * Updates this component by updating the text content.
  *
  * @param {ReactText} nextText The next text content
  * @param {ReactReconcileTransaction} transaction
  * @internal
  */
receiveComponent: function(nextText, transaction) {
  // ...
},

We try to keep existing annotations up-to-date but we don't enforce them. We don't use JSDoc in the newly written code, and instead use Flow to document and enforce types.

Flow #

We recently started introducing Flow checks to the codebase. Files marked with the @flow annotation in the license header comment are being typechecked.

We accept pull requests adding Flow annotations to existing code. Flow annotations look like this:

ReactRef.detachRefs = function(
  instance: ReactInstance,
  element: ReactElement | string | number | null | false,
): void {
  // ...
}

When possible, new code should use Flow annotations.
You can run npm run flow locally to check your code with Flow.

Classes and Mixins #

React was originally written in ES5. We have since enabled ES2015 features with Babel, including classes. However most of React code is still written in ES5.

In particular, you might see the following pattern quite often:

// Constructor
function ReactDOMComponent(element) {
  this._currentElement = element;
}

// Methods
ReactDOMComponent.Mixin = {
  mountComponent: function() {
    // ...
  }
};

// Put methods on the prototype
Object.assign(
  ReactDOMComponent.prototype,
  ReactDOMComponent.Mixin
);

module.exports = ReactDOMComponent;

The Mixin in this code has no relation to React mixins feature. It is just a way of grouping a few methods under an object. Those methods may later get attached to some other class. We use this pattern in a few places although we try to avoid it in the new code.

Equivalent code in ES2015 would like this:

class ReactDOMComponent {
  constructor(element) {
    this._currentElement = element;
  }

  mountComponent() {
    // ...
  }
}

module.exports = ReactDOMComponent;

Sometimes we convert old code to ES2015 classes. However this is not very important to us because there is an ongoing effort to replace the React reconciler implementation with a less object-oriented approach which wouldn't use classes at all.

Dynamic Injection #

React uses dynamic injection in some modules. While it is always explicit, it is still unfortunate because it hinders understanding of the code. The main reason it exists is because React originally only supported DOM as a target. React Native started as a React fork. We had to add dynamic injection to let React Native override some behaviors.

You may see modules declaring their dynamic dependencies like this:

// Dynamically injected
var textComponentClass = null;

// Relies on dynamically injected value
function createInstanceForText(text) {
  return new textComponentClass(text);
}

var ReactHostComponent = {
  createInstanceForText,

  // Provides an opportunity for dynamic injection
  injection: {
    injectTextComponentClass: function(componentClass) {
      textComponentClass = componentClass;
    },
  },
};

module.exports = ReactHostComponent;

The injection field is not handled specially in any way. But by convention, it means that this module wants to have some (presumably platform-specific) dependencies injected into it at runtime.

In React DOM, ReactDefaultInjection injects a DOM-specific implementation:

ReactHostComponent.injection.injectTextComponentClass(ReactDOMTextComponent);

In React Native, ReactNativeDefaultInjection injects its own implementation:

ReactHostComponent.injection.injectTextComponentClass(ReactNativeTextComponent);

There are multiple injection points in the codebase. In the future, we intend to get rid of the dynamic injection mechanism and wire up all the pieces statically during the build.

Multiple Packages #

React is a monorepo. Its repository contains multiple separate packages so that their changes can be coordinated together, and documentation and issues live in one place.

The npm metadata such as package.json files is located in the packages top-level folder. However there is almost no real code in it.

For example, packages/react/react.js re-exports src/isomorphic/React.js, the real npm entry point. Other packages mostly repeat this pattern. All the important code lives in src.

While the code is separated in the source tree, the exact package boundaries are slightly different for npm packages and standalone browser builds.

React Core #

The "core" of React includes all the top-level React APIs, for example:

  • React.createElement()
  • React.createClass()
  • React.Component
  • React.Children
  • React.PropTypes

React core only includes the APIs necessary to define components. It does not include the reconciliation algorithm or any platform-specific code. It is used both by React DOM and React Native components.

The code for React core is located in src/isomorphic in the source tree. It is available on npm as the react package. The corresponding standalone browser build is called react.js, and it exports a global called React.

Note:

Until very recently, react npm package and react.js standalone build contained all React code (including React DOM) rather than just the core. This was done for backwards compatibility and historical reasons. Since React 15.4.0, the core is better separated in the build output.

There is also an additional standalone browser build called react-with-addons.js which we will consider separately further below.

Renderers #

React was originally created for the DOM but it was later adapted to also support native platforms with React Native. This introduced the concept of "renderers" to React internals.

Renderers manage how a React tree turns into the underlying platform calls.

Renderers are located in src/renderers:

The only other officially supported renderer is react-art. To avoid accidentally breaking it as we make changes to React, we checked it in as src/renderers/art and run its test suite. Nevertheless its GitHub repository still acts as the source of truth.

While it is technically possible to create custom React renderer, this is currently not officially supported. There is no stable public contract for custom renderers yet which is another reason why we keep them all in a single place.

Note:

Technically the native renderer is a very thin layer that teaches React to interact with React Native implementation. The real platform-specific code managing the native views lives in the React Native repository together with its components.

Reconcilers #

Even vastly different renderers like React DOM and React Native need to share a lot of logic. In particular, the reconciliation algorithm should be as similar as possible so that declarative rendering, custom components, state, lifecycle methods, and refs work consistently across platforms.

To solve this, different renderers share some code between them. We call this part of React a "reconciler". When an update such as setState() is scheduled, the reconciler calls render() on components in the tree and mounts, updates, or unmounts them.

Reconcilers are not packaged separately because they currently have no public API. Instead, they are exclusively used by renderers such as React DOM and React Native.

Stack Reconciler #

The "stack" reconciler is the one powering all React production code today. It is located in src/renderers/shared/stack/reconciler and is used by both React DOM and React Native.

It is written in an object-oriented way and maintains a separate tree of "internal instances" for all React components. The internal instances exist both for user-defined ("composite") and platform-specific ("host") components. The internal instances are inaccessible directly to the user, and their tree is never exposed.

When a component mounts, updates, or unmounts, the stack reconciler calls a method on that internal instance. The methods are called mountComponent(element), receiveComponent(nextElement), and unmountComponent(element).

Host Components #

Platform-specific ("host") components, such as <div> or a <View>, run platform-specific code. For example, React DOM instructs the stack reconciler to use ReactDOMComponent to handle mounting, updates, and unmounting of DOM components.

Regardless of the platform, both <div> and <View> handle managing multiple children in a similar way. For convenience, the stack reconciler provides a helper called ReactMultiChild that both DOM and Native renderers use.

Composite Components #

User-defined ("composite") components should behave the same way with all renderers. This is why the stack reconciler provides a shared implementation in ReactCompositeComponent. It is always the same regardless of the renderer.

Composite components also implement mounting, updating, and unmounting. However, unlike host components, ReactCompositeComponent needs to behave differently depending on user's code. This is why it calls methods, such as render() and componentDidMount(), on the user-supplied class.

During an update, ReactCompositeComponent checks whether the render() output has a different type or key than the last time. If neither type nor key has changed, it delegates the update to the existing child internal instance. Otherwise, it unmounts the old child instance, and mounts a new one. This is described in the reconciliation algorithm.

Recursion #

During an update, the stack reconciler "drills down" through composite components, runs their render() methods, and decides whether to update or replace their single child instance. It executes platform-specific code as it passes through the host components like <div> and <View>. Host components may have multiple children which are also processed recursively.

It is important to understand that the stack reconciler always processes the component tree synchronously in a single pass. While individual tree branches may bail out of reconciliation, the stack reconciler can't pause, and so it is suboptimal when the updates are deep and the available CPU time is limited.

 Learn More #

The next section describes the current implementation in more details.

Fiber Reconciler #

The "fiber" reconciler is a new effort aiming to resolve the problems inherent in the stack reconciler and fix a few long-standing issues.

It is a complete rewrite of the reconciler, and is currently in active development.

Its main goals are:

  • Ability to split interruptible work in chunks.
  • Ability to prioritize, rebase and reuse work in progress.
  • Ability to yield back and forth between parents and children to support layout in React.
  • Ability to return multiple elements from render().
  • Better support for error boundaries.

You can read more about it in React Fiber Architecture. At this moment, it is still very experimental, and far from feature parity with the stack reconciler.

Its source code is located in src/renderers/shared/fiber.

Event System #

React implements a synthetic event system which is agnostic of the renderers and works both with React DOM and React Native. Its source code is located in src/renderers/shared/stack/event.

There is a video with a deep code dive into it (66 mins).

Add-ons #

Each of the React add-ons ships as a separate package on npm with a react-addons- prefix. Their source is located in src/addons with the exception of ReactPerf and ReactTestUtils.

Additionally, we provide a standalone build called react-with-addons.js which includes React core and all add-ons exposed on the addons field of the React global object.

What Next? #

Read the next section to learn about the current implementation of reconciler in more detail.