Docs
Manual Mode
Manual Mode Tutorial

Manual mode Tutorial

Let's learn how you can integrate Million.js into your React applications.

Million.js assumes that you're already familiar with and you're using React. If you're not, is is recommended you check out react.dev (opens in a new tab) first.

You will learn:

  • How to use block() to convert React components into blocks
  • How to use <For /> for efficiently rendering lists
  • When to use block() and <For />
  • The limitations of blocks

Blocks can be used just like a normal React component:

export default function App() {
  return (
    <div>
      <h1>mil + LION = million</h1>
      <LionBlock />
    </div>
  );
}

Have a look at the result:

Preview

With that in hand, let's build an app.

Data Grid Example

One use case of blocks is rendering lists of data efficiently. In this example, let's build a data grid in React.

You have access to the prebuilt components <Table /> and <Input /> from your fake user interface (UI) library. You can then store the number of rows you want to display in a useState() hook.

function App() {
  const [rows, setRows] = useState(1);
 
  return (
    <div>
      <Input value={rows} setValue={setRows} />
      <Table>// ...</Table>
    </div>
  );
}

But wait! You made a grid but you have no data. Let's say you can grab some array of arbitrary data from a function called buildData(rows):

const data = buildData(100);
// returns [{ adjective: '...', color: '...', noun: '...' }, ... x100]

Now, you can render the data in our table using Array.map():

function App() {
  const [rows, setRows] = useState(1);
  const data = buildData(rows);
 
  return (
    <div>
      <Input value={rows} setValue={setRows} />
      <Table>
        {data.map(({ adjective, color, noun }) => (
          <tr>
            <td>{adjective}</td>
            <td>{color}</td>
            <td>{noun}</td>
          </tr>
        ))}
      </Table>
    </div>
  );
}

Preview

You can see that it performs pretty well. From 0-100, there's virtually no lag, but once you get higher than 500 or so, there's a noticable delay in rendering.

React is great because you can declaratively write great UI and get pretty good performance. But the data grid you just made is a rudimentary example, and is not necessarily representative of most React applications.

More realistic rendering

In the following example, you add lotsOfElements (an array of a lot of blank elements) to each row. you can also add a lag radar to monitor page performance.

Try changing the input value up and down from 0 to 1000. Notice how React really struggles when rendering a lot of elements.

Editable example

import { useState } from 'react';
import { Table, Input, lotsOfElements } from './ui';
import { buildData } from './data';

function App() {
const [rows, setRows] = useState(1);
const data = buildData(rows);

  return (
    <div>
      <Input value={rows} setValue={setRows} />
      <Table showRadar>
        {data.map(({ adjective, color, noun }) => (
          <tr>
            <td>{adjective}</td>
            <td>{color}</td>
            <td>{noun}</td>
            <td>{...lotsOfElements}</td>
          </tr>
        ))}
      </Table>
    </div>
  );

}

export default App;

Preview

Just block it

In the following example, you can use block() and <For /> in order to optimize rendering.

First, you need to abstract the <tr> into its own component.

data.map(({ adjective, color, noun }) => (
  <tr>
    <td>{adjective}</td>
    <td>{color}</td>
    <td>{noun}</td>
    <td>{...lotsOfElements}</td>
  </tr>
));
 
// 👇👇👇
 
function Row({ adjective, color, noun }) {
  return (
    <tr>
      <td>{adjective}</td>
      <td>{color}</td>
      <td>{noun}</td>
      {...lotsOfElements}
    </tr>
  );
}

Then, you can wrap it with block() in order to optimize the <Row /> component.

import { block } from 'million/react';
 
const RowBlock = block(function Row({ adjective, color, noun }) {
  return (
    <tr>
      <td>{adjective}</td>
      <td>{color}</td>
      <td>{noun}</td>
      {...lotsOfElements}
    </tr>
  );
});

Once, you've optimized a row, you will need to render it as a list:

data.map(({ adjective, color, noun }) => (
  <RowBlock adjective={adjective} color={color} noun={noun}>
));

BUT WAIT! You can actually use Million.js' built-in rendering solution.

Optimized List Rendering

The <For /> component is used to render a list of blocks. It takes an array as the each prop and a function as its children. The function is called for each item in the array and is passed the item and its index as arguments.

<For /> Component

Syntax: <For each={array}>{(item, index) => Block}</For>
Example: <For each={[1, 2, 3]}>{(item, index) => myBlock({ item, index })}</For>

It's the best way to loop over an array (uses mapArray() under the hood). As the array changes, <For /> updates or moves items in the DOM rather than recreating them. Let's look at an example:

With this in mind, you can rewrite your table to use <For />:

import { For } from 'million/react';
 
<For each={data}>
  {({ adjective, color, noun }) => (
    <RowBlock adjective={adjective} color={color} noun={noun} />
  )}
</For>;

Now that we've integrated Million.js, let's check the new example out.

Notice when you change the input value, the lag radar shows significantly less lag than the pure React example. With a faster underlying virtual DOM, Million.js can take a lot of the pain out of rendering large lists.

Editable example

import { useState } from 'react';
import { Table, Input, lotsOfElements } from './ui';
import { buildData } from './data';
import { block, For } from 'million/react';

const RowBlock = block(
function Row({ adjective, color, noun }) {
return (
<tr>
<td>{adjective}</td>
<td>{color}</td>
<td>{noun}</td>
{...lotsOfElements}
</tr>
);
}
);

function App() {
const [rows, setRows] = useState(1);
const data = buildData(rows);

  return (
    <div>
      <Input value={rows} setValue={setRows} />
      <Table showRadar>
        <For each={data}>
          {({ adjective, color, noun }) => (
            <RowBlock adjective={adjective} color={color} noun={noun} />
          )}
        </For>
      </Table>
    </div>
  );

}

export default App;

Preview

Hitting the limit

⚠️

This section is a bit more advanced. If you want a list of limitations, check out the Rules of Blocks. Or, if you just want to start integrating Million.js, check out the installation guide.

Blocks are great for rendering large lists, data grids, and many other use cases. Under the hood, they render with the Million.js virtual DOM instead of React.

Deep Dive: How does it work?

We are given two virtual DOMs: current, which represents what our UI looks like, and new, which represents what we want.

(1/6)

Using a block can allow us to capture potential performance gains. However, you should always use best judgement, as blocks are not a silver bullet. Here are some general guidelines to follow:

  • Static views: Blocks perform best when there's not that much dynamic data. Since static parts of the React tree need to be unnecessary rerendered when dynamic data changes by React, blocks can directly skip to what's dynamic.
  • Nested data: Blocks are great for rendering nested data. Million.js turns tree traversal from O(tree) to O(1), allowing for fast access and changes.

Looking for the full guidelines? Check out Rules of Blocks.

Next Steps

By now, you know the basics of how to integrate Million.js into your application!

Check out the installation guide to put them into practice and start using blocks.


This page is directly inspired by React's "Quick Start" page (opens in a new tab).