block()
Syntax: block((props) => vnode)
Example: block((props) => <div>{props.foo}</div>)
Million.js is a library that enables you to create blocks. A block is a special Higher Order Component (HOC) (opens in a new tab) that can be used as a React component but is hyper-optimized for rendering speed.
Blocks are essentially components wrapped by block()
.
import { block } from 'million/react';
const LionBlock = block(function Lion() {
return <img src="https://million.dev/lion.svg" />;
});
export default LionBlock;
Custom Tags
The as
prop can be used to specify the tag name of the For. By default, it is slot
.
import { block } from 'million/react';
const LionBlock = block(
function Lion() {
return <img src="https://million.dev/lion.svg" />;
},
{ as: 'div' },
);
Using Block With SSR
If you are using Million.js on the server, you may encounter a hydration mismatch error. This is because Million.js uses a different algorithm for rendering on the server than it does on the client. To fix this, you can disable SSR.
const NoSSRBlock = block(
function NoSSR() {
return <div>{Math.random()}</div>;
},
{ ssr: false },
);
Rules Of Blocks
[Million.js] You did something wrong!
<div>
<YourBlock>Uh oh...</YourBlock>
^
</div>
There are three common reasons you might be seeing it:
- Breaking rules of blocks
- Not adding the compiler
- Encountering 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.
Advanced Information
This function is part of the internal API. You should only be using this if you are making your own framework.
block()
Syntax: block((props) => vnode)
Example: block((props) => <div>{props.foo}</div>)
The block
function instantiates a Block
(a stateless "component"). It accepts a function with a props
object parameter that returns a VNode
.
Rules of usage
props
is an immutable object with primitive or Block
values.
someBlock({
one: '1', // โ
two: 1 + 1, // โ
three: true, // โ
four: Date.now(), // โ
five: anotherBlock({ crewmate: true }), // โ
six: { imposter: true }, // โ
seven: new Date(), // โ
});
Top level values of props
may not be interpolated with other values.
The props
are filled with immutable Hole
values. These values are replaced with the actual values when the block()
is called.
// Anatomy of a `Hole`
{
$: 'prop';
}
// Example:
block((props) => {
console.log(props.foo); // { $: 'foo' } โ
console.log(props.foo + ' bar'); // { $: 'foo' } + ' bar' โ
return <div>{props.foo}</div>;
});
The following are examples of this rule:
block((props) => {
const { favorite } = props.favorite; // โ
<div className={props.className /* โ
*/}>
{props.hello /* โ
*/}
{Date.now() /* โ
*/}
<button
onClick={() => {
console.log(props.world); /* โ (no holes inside listeners) */
}}
>
{props.count + 1 /* โ */}
{props.foo.toString() /* โ */}
</button>
</div>;
});