Docs
Rules of Blocks

Rules of Blocks

You are probably here because you got a warning message in your console.

โš ๏ธ
[Million.js] You did something wrong!
App.jsx
<div>
  <YourBlock>Uh oh...</YourBlock>
            ^
</div>

There are three common reasons you might be seeing it:

  1. You might be breaking Rules of Blocks.
  2. You might have forgot to use the compiler.
  3. You've entered unsupported behavior.

Breaking Rules of Blocks

You may have heard of "progressive enhancement," (opens in a new tab) which is the idea that tools progressively use features based on what is supported. Similarly, Million.js has "progressive degradation," or the concept that if you use features that are not supported, it will degrade to default React rendering gracefully.

This section highlights some of the possible warnings you might encounter using blocks. Note that this is not an exhaustive list.

This section presents idiomatic patterns to use blocks. It is not a list of errors, your application will still work if you don't follow these patterns.

Declaring blocks

โš ๏ธ

[Million.js] Block needs to be defined as a variable declaration.

The above usually occurs when you have a block that is not declared as a variable. This prevents the compiler from analyzing the block correctly.

console.log(block(() => <div />)) // โŒ Wrong
export default block(() => <div />) // โŒ Wrong
 
// ๐Ÿ‘‡๐Ÿ‘‡๐Ÿ‘‡
 
const Block = block(() => <div />) // โœ… Correct
console.log(Block);
export default Block;

Calling block()

โš ๏ธ

[Million.js] Found unsupported argument for block. Make sure blocks consume a reference to a component function or the direct declaration.

The above usually occurs when you have an actual JSX component like <Component /> passed into the block() function instead of a reference to the Component itself.

const BadBlock = block(<Component />) // โŒ Wrong
 
const GoodBlock = block(App)  // โœ… Correct

Using <For /> instead of map()

โš ๏ธ

[Million.js] Array.map() will degrade performance. We recommend removing the block on the current component and using a <For /> component instead

The above will occur when you use <Array>.map within a block. This is not ideal, especially if the component that holds the list is a block. The right pattern is to remove the block on the current component and use a <For /> component instead for the children.

<div>
  {items.map((item) => (
    <div key={item}>{item}</div>
  ))}
</div>
 
// ๐Ÿ‘‡๐Ÿ‘‡๐Ÿ‘‡
 
<For each={items}>
  {(item) => <div key={item}>{item}</div>}
</For>

Deterministic returns

โš ๏ธ

[Million.js] Conditional expressions will degrade performance. We recommend using deterministic returns instead.

๐Ÿšซ

Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.

Returns must be "deterministic," meaning there can only be one return statement at the end of the block that returns a stable tree.

Some examples of non-deterministic returns:

function Component() {
  const [count, setCount] = useState(initial.count);
 
  if (count > 10) {
    return <div>Too many clicks!</div>; // โŒ Wrong
  }
 
  // โŒ Wrong
  return count > 5 ? (
    'Count is greater than 5'
  ) : (
    <div>Count is {count}.</div>
  );
}
 
const ComponentBlock = block(Component);

UI Component libraries ๐Ÿ™…โ€โ™€๏ธ

โš ๏ธ

[Million.js] Components will cause degraded performance. Ideally, you should use DOM elements instead.

Many React applications use UI component libraries like Material UI, Chakra UI, or Tailwind UI. These libraries are great, but they are not optimized for Million.js.

Million.js requires that you use DOM elements instead of components. This is because components can introduce non-deterministic returns, which can cause degraded performance.

// โŒ Bad
<Stack>
  <Text>What's up my fellow components</Text>
</Stack>
 
// ๐Ÿคจ Maybe
<div>
  <Text>What's up my fellow components</Text>
</div>
 
// โœ… Good
<div>
  <p>What's up my fellow components</p>
</div>

Spread attributes/children

โš ๏ธ

[Million.js] Spread attributes/children are not fully supported

You can't use spread attributes/children that change safely or reference a binding within the component inside Million.js, as they can introduce non-deterministic returns.

const arr = ['Hello'];
 
<div>{...arr}</div> // Ok if arr never changes
 
// โŒ Bad
arr.push('World');

Unsupported import

๐Ÿšซ

[Million.js] Found unsupported import for block. Make sure blocks are imported from million/react.

This may be caused by importing the block from the wrong place. Make sure you import the block from million/react instead of million.

import { block } from 'million'; // โŒ Wrong
 
import { block } from 'million/react'; // โœ… Correct

Using the compiler

๐Ÿšซ

Warning: Invalid Hook Call. Hooks can only be called inside of the body of a function component.

You may have forgot to use the compiler, a necessary part in ensuring that your JSX is compiled to Million.js compatible code. You can view the instructions at the installation guide.

On a side note, Million.js is technically usable without the compiler, but it's significantly more limited in scope, and there is a more limited set of features available. This is not recommended.

Unsupported behavior

๐Ÿšซ

Uncaught Error: ??? :(

If none of this worked, please create an issue (opens in a new tab) and we'll try to help. Try to create a small reproducing example โ€” you might discover the problem as you're doing it.