Mitosis is a subset of JSX. It supports generating code for a number of frontend frameworks, including React, Vue, Angular, Svelte, and Solid.
Table of contents
- At a glance
- Components
- Styling
- State
- Methods
- Control flow
- Refs
- onMount
- onUnMount
- Gotchas and limitations
Mitosis is inspired by many modern frameworks. You'll see components look like React components and use React-like hooks, but have simple mutable state like Vue, use a static form of JSX like Solid, compile away like Svelte, and uses a simple, prescriptive structure like Angular.
An example Mitosis component showing several features:
import { For, Show, useState } from '@builder.io/mitosis';
export default function MyComponent(props) {
const state = useState({
newItemName: 'New item',
list: ['hello', 'world'],
addItem() {
state.list = [...state.list, state.newItemName];
},
});
return (
<div>
<Show when={props.showInput}>
<input
value={state.newItemName}
onChange={(event) => (state.newItemName = event.target.value)}
/>
</Show>
<div css={{ padding: '10px' }}>
<button onClick={() => state.addItem()}>Add list item</button>
<div>
<For each={state.list}>{(item) => <div>{item}</div>}</For>
</div>
</div>
</div>
);
}
Mitosis is component-driven like most modern frontend frameworks. Each Mitosis component should be in its own file and be the single default export. They are simple functions that return JSX elements
export default function MyComponent() {
return <div>Hello world!</div>;
}
Styling is done via the css
prop on dom elements and components. It takes CSS properties in camelCase
(like the style
object on DOM elements) and properties as valid CSS strings
export default function CSSExample() {
return <div css={{ marginTop: '10px', color: 'red' }} />;
}
You can also include media queries as keys, with values as style objects
export default function ResponsiveExample() {
return (
<div
css={{
marginTop: '10px',
'@media (max-width: 500px)': {
marginTop: '0px',
},
}}
/>
);
}
State is provided by the useState
hook. Currently, the name of this value must be state
like below:
export default function MyComponent() {
const state = useState({
name: 'Steve',
});
return (
<div>
<h2>Hello, {state.name}</h2>
<input
onInput={(event) => (state.name = event.target.value)}
value={state.name}
/>
</div>
);
}
Components automatically update when state values change
The state object can also take methods.
export default function MyComponent() {
const state = useState({
name: 'Steve',
updateName(newName) {
state.name = newName;
},
});
return (
<div>
<h2>Hello, {state.name}</h2>
<input
onInput={(event) => state.updateName(event.target.value)}
value={state.name}
/>
</div>
);
}
Control flow in Builder is static like Solid. Instead of using freeform javascript like in React, you must use control flow components like <Show>
and <For>
Use <Show>
for conditional logic. It takes a singular when
prop for a condition to match for. When the condition is truthy, the children will render, otherwise they will not
export default function MyComponent(props) {
return <Show when={props.showContents}>Hello, I may or may not show!</Show>;
}
Use <For>
for repeating items, for instance mapping over an array. It takes a singular each
prop for the array to iterate over. This component takes a singular function as a child that it passes the relevant item and index to, like below:
export default function MyComponent(props) {
const state = useState({
myArray: [1, 2, 3],
});
return (
<For each={state.myArray}>
{(theArrayItem, index) => <div>{theArrayItem}</div>}
</For>
);
}
Use the useRef
hook to hold a reference to a rendered DOM element.
import { useState } from '@builder.io/mitosis';
export default function MyComponent() {
const state = useState({
name: 'Steve',
onBlur() {
// Maintain focus
inputRef.focus();
},
get lowerCaseName() {
return state.name.toLowerCase();
},
});
const inputRef = useRef();
return (
<div>
<Show when={props.showInput}>
<input
ref={inputRef}
css={{ color: 'red' }}
value={state.name}
onBlur={() => state.onBlur()}
onChange={(event) => (state.name = event.target.value)}
/>
</Show>
Hello {state.lowerCaseName}! I can run in React, Vue, Solid, or Liquid!
</div>
);
}
The onMount hook is the best place to put custom code to execute once the component mounts.
export default function MyComponent() {
onMount(() => {
alert('I have mounted!');
});
return <div>Hello world</div>;
}
The onUnMount hook is the best place to put any cleanup you need to do when a component is removed
export default function MyComponent() {
onUnMount(() => {
alert('I have been removed!');
});
return <div>Hello world</div>;
}
Mitosis input
export default function MyComponent() {
const state = useState({
foo: 'bar',
doSomething() {
const foo = state.foo;
},
});
}
Mitosis output
import { useState } from 'react';
export default function MyComponent(props) {
const [foo, setFoo] = useState(() => 'bar');
function doSomething() {
const foo = foo;
}
return <></>;
}
Work around
Use a different variable name
Mitosis input
export default function MyComponent() {
const state = useState({
foo: 'bar',
doSomething() {
const foo_ = state.foo;
},
});
}
Mitosis output
import { useState } from 'react';
export default function MyComponent(props) {
const [foo, setFoo] = useState(() => 'bar');
function doSomething() {
const foo_ = foo;
}
return <></>;
}
Mitosis input
export default function MyComponent() {
const state = useState({
async doSomethingAsync(event) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// Fails to parse this line
return;
},
});
}
Work around
You can either:
a. Use promises in this context instead or b. Use an immediately invoked async function
Mitosis input
export default function MyComponent() {
const state = useState({
doSomethingAsync(event) {
void (async function() {
const response = await fetch(); /* ... */
})();
},
});
}
Mitosis output
import { useState } from 'react';
export default function MyComponent(props) {
function doSomethingAsync(event) {
void (async function() {
const response = await fetch();
})();
}
return <></>;
}
Regardless of what you make name the argument to a callback, it will be
renamed in the output to event
.
Mitosis input
export default function MyComponent() {
return <input onClick={(e) => myCallback(e)} />;
}
Mitosis output
export default function MyComponent(props) {
return (
<>
<input
onClick={(event) => {
myCallback(e);
}}
/>
</>
);
}
Mitosis input
export default function MyComponent() {
const state = useState({
myCallback(event) {
// do something
},
});
return <input onClick={state.myCallback} />;
}
Mitosis output
import { useState } from 'react';
export default function MyComponent(props) {
function myCallback(event) {
// do something
}
return (
<>
<input
onClick={(event) => {
myCallback;
}}
/>
</>
);
}
Work around
Define an anonymous function in the callback
Mitosis input
export default function MyComponent() {
const state = useState({
myCallback(event) {
// do something
},
});
return <input onClick={(event) => state.myCallback(event)} />;
}
Mitosis output
import { useState } from 'react';
export default function MyComponent(props) {
function myCallback(event) {
// do something
}
return (
<>
<input
onClick={(event) => {
myCallback(event);
}}
/>
</>
);
}
JSX lite parsing fails on referencing props
in a call to useState
.
Mitosis input
export default function MyComponent(props) {
const state = useState({ text: props.text });
// ^^^^^^^^^^
// Could not JSON5 parse object
}
Work around
Use onMount:
Mitosis input
export default function MyComponent(props) {
const state = useState({ text: null });
onMount(() => {
state.text = props.text;
});
}
Mitosis output
import { useState } from 'react';
export default function MyComponent(props) {
const [text, setText] = useState(() => null);
useEffect(() => {
setText(props.text);
}, []);
return <></>;
}
Destructuring assignment from state
isn't currently supported, and is
ignored by the compiler.
Mitosis input
export default function MyComponent() {
const state = useState({ foo: '1' });
onMount(() => {
const { foo } = state;
});
}
Mitosis output
import { useState } from 'react';
export default function MyComponent(props) {
const [foo, setFoo] = useState(() => '1');
useEffect(() => {
const { foo } = state;
}, []);
return <></>;
}
Work around
Use standard assignment instead for now.
Mitosis input
export default function MyComponent() {
const state = useState({ foo: '1' });
onMount(() => {
const foo = state.foo;
});
}
Mitosis output
import { useState } from 'react';
export default function MyComponent(props) {
const [foo, setFoo] = useState(() => '1');
useEffect(() => {
const foo = foo;
}, []);
return <></>;
}