Skip to content

Latest commit

 

History

History
586 lines (449 loc) · 11.7 KB

overview.md

File metadata and controls

586 lines (449 loc) · 11.7 KB

Mitosis Overview

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

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>
  );
}

Components

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

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

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

Methods

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

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>

Show

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>;
}

For

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>
  );
}

Refs

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>
  );
}

onMount

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>;
}

onUnMount

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>;
}

Gotchas and limitations

Defining variables with the same name as a state property will shadow it

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 <></>;
}

Async methods can't be defined on "state"

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 <></>;
}

Callback implicitly have an "event" arg

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);
        }}
      />
    </>
  );
}

Functions can't be passed by reference to JSX callbacks

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);
        }}
      />
    </>
  );
}

Can't assign to "params" to "state"

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 <></>;
}

Can't destructure assignment from state

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 <></>;
}