---
title: 'Core Components'
source: 'https://academia.sh/en/courses/react-native/core-components'
course: 'React Native'
language: en
updated: '2026-10-21T05:02:54+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Recognizes the view, text, image, and input field as typed nodes in the tree'
    - 'Measures that a prop change on the same type leaves the node updated'
    - 'Shows that when the type changes, the old node comes out unmounted and the new one mounted, and the props are not examined'
    - 'Separates the path of data through the tree from the fate at the node the data reaches'
---

# Core Components

The core components are view, text, image, and input field; each is a node in the view tree. The lesson measures what a type change does to the tree in the update where a placeholder text is replaced by an image.

The previous lesson asked where the first tree comes from and found the home screen mounted with its whole subtree. This lesson looks inside that home screen. A screen is built from a few core types the framework offers ready-made: a container view, text, an image, and a field where text is entered. Each is a node in the tree, and the node's type is the third question of the fate rule: if a node of a different type arrives in the same place, the old one is unmounted and the new one is mounted.

The snippet below is the description of a profile card. The snippet is not measured; the model does not run the framework's code.

```jsx
// Platform code: Profile
function Profile({ name, photo }) {
  return (
    <View>
      {photo ? <Image source={photo} /> : <Text>Loading</Text>}
      <Text>{name}</Text>
      <TextInput value={name} />
    </View>
  );
}
```

`View` is a container and shows nothing on its own; it holds its children together. `Text` shows text. `Image` is drawn from an image source. `TextInput` is the field where the user enters text. The function returns an `Image` if the image is ready and a placeholder `Text` if not; the name arrives as a `Text` in both cases. These four types are the core components covered in this lesson; later lessons add new ones with touch and platform selection.

## Four Types, One Rule

Each of the core types corresponds to a view on the native side. When shared code describes a `Text` node, the platform's own text view is created on iOS and on Android. The native view depends on the node's type; if the type changes, the native view must change too. A text view cannot turn into an image view in place. That is why the rule's third question looks at the type, and if the type differs it does not look at the props: the old subtree is unmounted and the new subtree is mounted.

The core is needed first for the measurement. The comparison core is given again in every lesson in the same form.

```javascript
// fate.mjs — classifies what an update does to each node of the view tree
export const FATES = ["kept", "updated", "mounted", "unmounted"];

export function el(type, props = {}, children = [], key = null) {
  return { type, props, children, key };
}

function segment(node) {
  return node.key === null ? node.type : `${node.type}[${node.key}]`;
}

function identity(node, index) {
  return node.key === null ? `#${index}` : `key:${node.key}`;
}

function sameProps(a, b) {
  const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
  return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n]));
}

function whole(node, path, fate, ops) {
  const here = `${path}/${segment(node)}`;
  ops.push({ fate, path: here });
  node.children.forEach((child) => whole(child, here, fate, ops));
}

export function reconcile(prev, next, path = "", ops = []) {
  if (prev === null) {
    whole(next, path, "mounted", ops);
    return ops;
  }
  if (next === null) {
    whole(prev, path, "unmounted", ops);
    return ops;
  }
  if (prev.type !== next.type || prev.key !== next.key) {
    whole(prev, path, "unmounted", ops);
    whole(next, path, "mounted", ops);
    return ops;
  }
  const here = `${path}/${segment(next)}`;
  ops.push({ fate: sameProps(prev.props, next.props) ? "kept" : "updated", path: here });
  const before = new Map(prev.children.map((child, i) => [identity(child, i), child]));
  const matched = new Set();
  next.children.forEach((child, i) => {
    const id = identity(child, i);
    matched.add(id);
    reconcile(before.has(id) ? before.get(id) : null, child, here, ops);
  });
  prev.children.forEach((child, i) => {
    if (!matched.has(identity(child, i))) reconcile(child, null, here, ops);
  });
  return ops;
}

export function printOps(ops) {
  ops.forEach((op) => console.log(`${op.fate}: ${op.path}`));
}

export function countFates(ops) {
  for (const name of FATES) {
    console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`);
  }
  console.log(`commands: ${ops.filter((op) => op.fate !== "kept").length}`);
}
```

This lesson's helper file produces a view tree from the profile state. The card is a container; its first child is an `Image` if the image is ready and a placeholder `Text` if not. The key of the text that carries the name is `name`, so its place is not affected by changes among its siblings. The input field carries the biography. The second function is an alternative description that makes the placeholder an image too; it is used below for comparison. The image's accessibility label carries the name; for this reason the name can appear in two places on the card.

```javascript
// profile.mjs — produces a view tree from the profile state
import { el } from "./fate.mjs";

export function renderProfile(state) {
  const slot = state.photo === null
    ? el("Text", { value: "Loading" })
    : el("Image", { source: state.photo, label: state.name });
  return el("Card", {}, [
    slot,
    el("Text", { value: state.name }, [], "name"),
    el("Input", { value: state.bio }),
  ]);
}

export function renderProfileImage(state) {
  return el("Card", {}, [
    el("Image", { source: state.photo, label: state.name }),
    el("Text", { value: state.name }, [], "name"),
    el("Input", { value: state.bio }),
  ]);
}
```

## Same Type, Changed Prop

In the first measurement the user changes the name. The image has not loaded yet; the placeholder stands. Because the type and key stay the same on every node, the rule looks at the props.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderProfile } from "./profile.mjs";

const before = renderProfile({ name: "Ayse", photo: null, bio: "Loves coffee" });
const after = renderProfile({ name: "Ayse Yilmaz", photo: null, bio: "Loves coffee" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
kept: /Card
kept: /Card/Text
updated: /Card/Text[name]
kept: /Card/Input
kept: 3
updated: 1
mounted: 0
unmounted: 0
commands: 1
```

Of four nodes, three came out kept and one updated; the commands are one. `/Card/Text[name]` stood in the same place, with the same type and the same key, but its `value` prop changed, so it became updated. The placeholder `Text` and the input field did not change, and no command went to the native side.

## Where the Data Arrives

We make the same name change while the image is loaded. The name now reaches two places: the text node and the image's label.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderProfile } from "./profile.mjs";

const before = renderProfile({ name: "Ayse", photo: "profile.png", bio: "Loves coffee" });
const after = renderProfile({ name: "Ayse Yilmaz", photo: "profile.png", bio: "Loves coffee" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
kept: /Card
updated: /Card/Image
updated: /Card/Text[name]
kept: /Card/Input
kept: 2
updated: 2
mounted: 0
unmounted: 0
commands: 2
```

In the first measurement the same change updated one node, here two. The data is the same, a single state field. Its path through the tree is also the same: from the state held at the root down to the card's children. What changes is what happens where the data arrives. When there was a placeholder, the name reached only the text node; when there is an image, it reaches the image's label too.

This is separate from the question we asked in M31/K03, "in which direction is this data going in the UI tree". There, the data's direction in the tree was classified: down or up. This course classifies not the data's path but what happens at the node it reaches. Two pieces of data with the same direction may update a node or may not affect any; two pieces of data with different directions may also give the same fate.

**CS4.** The model counts the input as controlled: the input field's value is a prop in the tree. A real input field also keeps the typed text on the native side; if the text and the value in the tree drift apart, the rule does not see it. This is an application of the limit from the first lesson: what is not visible in the tree receives no fate.

## When the Type Changes

Now the image loads and takes the placeholder's place. The card's first child stands in the same place, but its type goes from `Text` to `Image`. The rule stops at the third question: the type is different, the props are not examined.

## Reading the Snippet Line by Line

We return once more to the profile card snippet given at the start. The `Profile` function takes two inputs: `name` and `photo`. The root of the tree it returns is `View`; its children are, in order, an image or a placeholder, a text, and an input field. The ternary operator selects `Image` if `photo` is set and `Text` if not. The function contains a condition, but the tree does not carry the condition: only the result of the selection is in the tree. The difference between one description and the next is read according to which branch of this condition was selected. The `value` prop given to `TextInput` is the input's content; what the user typed and what stands in the tree need not be the same, and this is stated in the assumption above.

Here is a misconception to clear up: "When the image loads, the placeholder and the image are the same node, only its content changes." In the reader's eye the two stand in the same corner of the card and represent the same thing. In the rule's eye, a `Text` and an `Image` are two separate things: on the native side one is a text view and the other an image view, and it is not possible to turn one into the other. Even if the node's position in the tree is the same, its identity comes with its type.

## If the Placeholder Were an Image Too

For a counterexample, we change the description: the placeholder becomes an `Image` too, only with an empty source. When the image arrives, the type stays the same and only the source changes.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderProfileImage } from "./profile.mjs";

const before = renderProfileImage({ name: "Ayse", photo: null, bio: "Loves coffee" });
const after = renderProfileImage({ name: "Ayse", photo: "profile.png", bio: "Loves coffee" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
kept: /Card
updated: /Card/Image
kept: /Card/Text[name]
kept: /Card/Input
kept: 3
updated: 1
mounted: 0
unmounted: 0
commands: 1
```

The same change for the same user, with this description, is a single command. The placeholder's native view was not unmounted; the same image view received the new source. The rule does not know the intent inside the description, it looks only at the type and the prop; if the person writing the description wants to see which type changes can be avoided, the measurement tells them. It cannot be said that every placeholder can be an image: turning a loading text into an image changes the meaning. But knowing that a type change has a cost makes it possible to make a choice while writing the description.

## The Bounding Measurement

The focus node is the old child `/Card/Text`. When the image loads, the `photo` value in the card's state goes from `null` to a source, and the first child becomes `Image` in place of the placeholder `Text`.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderProfile } from "./profile.mjs";

const before = renderProfile({ name: "Ayse", photo: null, bio: "Loves coffee" });
const after = renderProfile({ name: "Ayse", photo: "profile.png", bio: "Loves coffee" });

const ops = reconcile(before, after);
printOps(ops);
countFates(ops);
```

```text
kept: /Card
unmounted: /Card/Text
mounted: /Card/Image
kept: /Card/Text[name]
kept: /Card/Input
kept: 3
updated: 0
mounted: 1
unmounted: 1
commands: 2
```

`/Card/Text` came out unmounted and `/Card/Image` mounted; the commands are two. The two nodes are in the same position, but because their types differ, the rule did not count them as two states of the same node. No comparison was made between the placeholder's props and the image's props. The name text met its own counterpart thanks to its key and stayed kept; the type change next to it did not touch it. The input field stayed kept too.

The cost of an unmount is not only the command count. The native view of the placeholder `Text` and every piece of state it holds disappear; the new `Image` inherits nothing. The placeholder had no state, so nothing was lost here. If the same change had swapped an input field with a text view, the cursor position and selection in the field would have disappeared. This loss is not visible in the tree and is not measured in the model; it is stated in prose.

Now, for comparison, we do the opposite: the type stays the same and only the image's source changes.

```javascript
// main.mjs
import { reconcile, printOps, countFates } from "./fate.mjs";
import { renderProfile } from "./profile.mjs";

const before = renderProfile({ name: "Ayse", photo: "profile.png", bio: "Loves coffee" });
const after = renderProfile({ name: "Ayse", photo: "profile2.png", bio: "Loves coffee" });

const ops = reconcile(before, after);
printOps(ops.filter((op) => op.fate !== "kept"));
countFates(ops);
```

```text
updated: /Card/Image
kept: 3
updated: 1
mounted: 0
unmounted: 0
commands: 1
```

`/Card/Image`, standing in the same place, with the same type and the same key, came out updated; the commands are one. The image's native view stayed in place and received the new source. The difference between a type change and a prop change is visible in the command count: the first is two commands, the second one command, and the native view's identity lives on only in the second. If we do not want to pay for one of the two commands, we can make the placeholder an `Image` too. If the placeholder is an `Image`, when the image arrives the type stays the same and only the source prop changes. This is a design choice, and how it looks in the tree is read from these measurements.

**CS5.** The model makes the type comparison over the name. A real framework counts two different description functions as different and uses function identity; the model reduces this subtlety to the type name. The model builds this fate's rule; the model is not the framework itself.

The common lesson of these measurements is this: what determines a node's life is not how it looks on the screen but with which type and which key it is written in the description. Two different looks can be different props of the same type; two similar looks can be different types. When choosing the type, the person writing the description is really choosing this: will this node live as a text view or as an image view? Keeping what will stay the same throughout its life in a single type makes updates cheap; keeping what will live differently in separate types means knowingly accepting unmounting and mounting.

## Summary

- This lesson's fate: unmounted. In the bounding measurement, the placeholder `/Card/Text` came out unmounted without its props being examined because its type changed when the image arrived; `/Card/Image` was mounted in its place.
- The core components covered in this lesson are view, text, image, and input field; each is a typed node in the tree and corresponds to a view of its type on the native side.
- A prop change on the same type leaves the node updated: when the name changed, `/Card/Text[name]` was updated and the commands stayed at one.
- The same data change gives a different fate depending on the node it reaches: the name updated one node with the placeholder and two with the image.
- The type change produced two commands (one unmounted, one mounted); when only the image's source changed, one command went and the node was updated.

## Next Step

In this lesson the props were plain values such as text and a source. The next lesson, `The Style System`, covers the style that determines a node's look: style also enters the tree as a prop, and the lesson measures what a new style object whose content stays the same does in the tree.
