Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 118 additions & 3 deletions docs/jsx.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ Explanation, examples, and build notes on how to use JSX in your Mithril.js-base
# JSX

- [Description](#description)
- [Setup](#setup)
- [Setup JSX](#setup-jsx)
- [Production build](#production-build)
- [Using Babel with Webpack](#using-babel-with-webpack)
- [Setup TSX](#setup-tsx-jsx-in-typescript)
- [Enabling Fragments](#enable-fragments)
- [Using Closure Components in TSX](#using-closure-components-in-tsx)
- [Differences with React](#differences-with-react)
- [JSX vs hyperscript](#jsx-vs-hyperscript)
- [Tips and Tricks](#tips-and-tricks)
Expand Down Expand Up @@ -58,9 +61,9 @@ m.render(document.body, <MyComponent />)

---

### Setup
### Setup JSX
Comment thread
JodliDev marked this conversation as resolved.
Outdated

The simplest way to use JSX is via a [Babel](https://babeljs.io/) plugin.
When using JavaScript, the simplest way to use JSX is via a [Babel](https://babeljs.io/) plugin. When using using [TypeScript](https://www.typescriptlang.org/) follow the [instructions below](#setup-tsx-jsx-in-typescript)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move the "when using TypeScript" to the next section.

Suggested change
When using JavaScript, the simplest way to use JSX is via a [Babel](https://babeljs.io/) plugin. When using using [TypeScript](https://www.typescriptlang.org/) follow the [instructions below](#setup-tsx-jsx-in-typescript)
When using JavaScript, the simplest way to use JSX is via a [Babel](https://babeljs.io/) plugin.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I shortened it. But I do think its wise to link to TSX in this secion. A lot of times the term "JSX" is used for "TSX" interchangably (so is "JavaScript" for "TypeScript") and TypeScript users might stumble over this section and follow the wrong instructions. Tell me if you find that unnecessary and I should fully remove it


Babel requires npm, which is automatically installed when you install [Node.js](https://nodejs.org/en/). Once npm is installed, create a project folder and run this command:

Expand Down Expand Up @@ -241,6 +244,118 @@ See [the Webpack docs](https://webpack.js.org/plugins/provide-plugin/) for more

---

### Setup TSX (JSX in TypeScript)

Since TypeScript is already transpiled, Babel is not necessary. All you need to do is tell TypeScript how to handle JSX code correctly (more information about JSX in TypeScript [here](https://www.typescriptlang.org/docs/handbook/jsx.html)).
Comment thread
JodliDev marked this conversation as resolved.
Outdated

Add `jsx` and `jsxFactory` to `compilerOptions` in your `tsconfig.json`:

```json
{
"compilerOptions": {
"jsx": "react",
"jsxFactory": "m"
}
}
```

This setup should be enough to get most JSX functionality working. But there are a few gotchas that you might want to fix as well:

#### Enabling Fragments
Comment thread
JodliDev marked this conversation as resolved.
Outdated

With the setup above, you will not be able to use Fragments (e.g. `<>bla</>`).
To enable Fragments, first add `jsxFragmentFactory` to `compilerOptions` in your `tsconfig.json`:

```json
{
"compilerOptions": {
"jsxFragmentFactory": "m.fragment"
}
}
```
`m.fragment` also needs to be defined globally. The easiest way of doing that, is adding the following line to the entry point of your application (depending on your project structure, that might be `src/index.ts`):

```typescript
m.fragment = { view: (vNode: Vnode) => vNode.children } as any;
```
Comment thread
JodliDev marked this conversation as resolved.
Outdated

#### Using Closure Components in TSX
Comment thread
JodliDev marked this conversation as resolved.
Outdated
When using [Closure Components](components.md#closure-component-state) in JSX, TypeScript only expects an attribute object as a parameter for a Function Component. But Mithril provides a `Vnode` object instead. This leads to the IDE showing faulty parameters even though the JSX would compile correctly.
Comment thread
JodliDev marked this conversation as resolved.
Outdated

Example:
The following code will compile correctly but show this error:
```
TS2739: Type { greet: string; } is missing the following properties from type Vnode<{}, {}>: tag, attrs, state
TS2786: LoadingSpinner cannot be used as a JSX component.
```

```typescript jsx
Comment thread
JodliDev marked this conversation as resolved.
Outdated
interface Attributes {
greet: string
}
function ChildComponent(vNode: Vnode<Attributes>): m.Component<Attributes> {
return {
view: <div>{vNode.attrs.greet}</div>
Comment thread
JodliDev marked this conversation as resolved.
Outdated
};
}

function ParentComponent() {
return {
view: <div><ChildComponent greet="Hello World"/></div> //This line will compile correctly but shows the errors above
};
}
Comment on lines +273 to +288
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As requested in the previous review.

Suggested change
interface Attributes {
greet: string
}
function ChildComponent(vNode: Vnode<Attributes>): m.Component<Attributes> {
return {
view: () => <div>{vNode.attrs.greet}</div>
};
}
function ParentComponent() {
return {
view: () => <div>
<ChildComponent greet="Hello World"/>
</div>
};
}
interface Attributes {
greet: string
}
function ChildComponent(vNode: Vnode<Attributes>): m.Component<Attributes> {
return {
view: () => <div>{vNode.attrs.greet}</div>
}
}
function ParentComponent() {
return {
view: () => <div>
<ChildComponent greet="Hello World"/>
</div>
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you want this change? As fas as I can see, semicolons after blocked return statements seem to be in line with the rest of the doc ( e.g. line 27, 396, 452, 481, 535)

```
Comment thread
JodliDev marked this conversation as resolved.
Outdated

There are several options to circumvent that problem:
Comment thread
JodliDev marked this conversation as resolved.
Outdated
1) Instead of `<div><ChildComponent greet="Hello World"/></div>`, use `<div>{m(ChildComponent, {greet: "Hello World"})}</div>` instead.
2) Use [Class Components](components.md#class-component-state) instead. Class Components will not show any errors. But TypeScript will not be able to autocomplete or inspect attributes (in this example `greet` would be unknown when used in `ParentComponent`).
3) Create a "translation function" (see `TranslatedComponent()` in the example below) to trick TypeScript.
Comment thread
JodliDev marked this conversation as resolved.
Outdated

The following code will work without errors:

```typescript jsx
Comment thread
JodliDev marked this conversation as resolved.
Outdated
/**
* Use TranslatedComponent to create Closure Components that can be inspected by TypeScript.
*/
export function TranslatedComponent<T>(create: m.ClosureComponent<T>) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you come up with a shorter name for this helper? Maybe something like component could work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that I chose a poor name for that helper function. But I think component would not be a good idea since it already exists in Mithril.js and also does not really make it clear what it does. I changed it to TsClosureComponent which hopefully is a bit more clear. It is not really shorter so I could also change it to TsClosure for example. But I find TsClosureComponent more descriptive.
Let me know what you think

return (attrs: T) => {
const vNode = attrs as m.Vnode<T>;
return create(vNode) as unknown as JSX.Element;
}
Comment thread
JodliDev marked this conversation as resolved.
Outdated
}


interface Attributes {
greet: string
}
//We slightly altered the definition of ChildComponent by using TranslatedComponent()
Comment thread
JodliDev marked this conversation as resolved.
Outdated
const ChildComponent = TranslationdComponent<Attributes>(vNode => {
Comment thread
JodliDev marked this conversation as resolved.
Outdated
return {
view: <div>{vNode.attrs.greet}</div>
Comment thread
JodliDev marked this conversation as resolved.
Outdated
};
})

function ParentComponent() {
return {
view: <div><ChildComponent greet="Hello World"/></div>
Comment thread
JodliDev marked this conversation as resolved.
Outdated
};
}
Comment on lines +314 to +330
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As requested in the previous review.

Suggested change
interface Attributes {
greet: string
}
// We slightly altered the definition of `ChildComponent` by using `TsClosureComponent`
const ChildComponent = TsClosureComponent<Attributes>(vNode => {
return {
view: () => <div>{vNode.attrs.greet}</div>
};
})
function ParentComponent() {
return {
view: () => <div>
<ChildComponent greet="Hello World"/>
</div>
};
}
interface Attributes {
greet: string
}
// We slightly altered the definition of `ChildComponent` by using `TsClosureComponent`
const ChildComponent = TsClosureComponent<Attributes>(vNode => {
return {
view: () => <div>{vNode.attrs.greet}</div>
}
})
function ParentComponent() {
return {
view: () => <div>
<ChildComponent greet="Hello World"/>
</div>
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure this change is needed. See comment above


```

When you need generics for your closure component, you can use the following definition style:
Comment thread
JodliDev marked this conversation as resolved.
Outdated

```typescript jsx
function ChildComponentImpl<T>() {
...
Comment thread
JodliDev marked this conversation as resolved.
Outdated
}

const ChildComponent = TranslatedComponent(ChildComponentImpl); //for TranslatedComponent, see above

const jsx = <div><ChildComponent<SomeClass>/></div>
Comment thread
JodliDev marked this conversation as resolved.
Outdated
```


### Differences with React

JSX in Mithril has some subtle but important differences compared to React's JSX.
Expand Down