Skip to content

Commit 862543f

Browse files
Add user-defined components how-to (#1557)
* Add user-defined components how-to * Properly indent code samples in list * link from JSX component page to user-defined components page * Remove Button from Insight tsx component * from > to * Fix Address example * use valid ethereum addresses in examples * add a section on spreading props * edit content --------- Co-authored-by: Alexandra Tran <alexandratran@protonmail.com>
1 parent 96cc0f7 commit 862543f

File tree

2 files changed

+263
-1
lines changed

2 files changed

+263
-1
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
---
2+
description: Create your own JSX components to improve readability.
3+
sidebar_position: 5
4+
---
5+
6+
# User-defined components
7+
8+
When using [Custom UI with JSX](with-jsx.md), you can create your own components by composing
9+
existing components or other user-defined components.
10+
11+
## Basic example
12+
13+
In this first, basic example, the user-defined component is static.
14+
It does not accept any props (parameters) and returns the contents of a static home page.
15+
16+
```jsx title="Home.jsx"
17+
import { Box, Heading, Text } from "@metamask/snaps-sdk/jsx";
18+
19+
export const Home = () => {
20+
return (
21+
<Box>
22+
<Heading>Welcome to my Snap</Heading>
23+
<Text>Hello, world!</Text>
24+
</Box>
25+
);
26+
};
27+
```
28+
29+
Once the component is defined, you can use it anywhere in the Snap.
30+
For example, to display the home page, you can use the following code:
31+
32+
```jsx title="index.jsx"
33+
import { Home } from "./Home";
34+
35+
export const onHomepage = () => {
36+
return <Home />;
37+
};
38+
```
39+
40+
## Example with props
41+
42+
You can parameterize components by passing props.
43+
Props are passed to the component as an object and can be accessed using the first parameter of the
44+
component's definition function:
45+
46+
```jsx title="Insight.jsx"
47+
export const Insight = (props) => {
48+
return (
49+
<Box>
50+
<Row label="From">
51+
<Address address={props.from} />
52+
</Row>
53+
<Row label="To">
54+
{to ? <Address address={props.to} /> : <Text>None</Text>}
55+
</Row>
56+
</Box>
57+
);
58+
};
59+
```
60+
61+
This example contains two usages of props:
62+
63+
- The `Insight` component accepts a `props` parameter, which is an object containing the `from` and
64+
`to` addresses.
65+
The `from` address is accessed using `props.from`, and the `to` address is accessed using
66+
`props.to`, since `props` is just a regular JavaScript object.
67+
- The `Insight` component then uses the built-in `Address` component to display addresses.
68+
The `Address` component accepts an `address` prop.
69+
When using the `Address` component, you can pass props to it using a notation similar to HTML
70+
attributes: `address={props.from}`.
71+
72+
To use the `Insight` component, you can pass the `from` and `to` addresses as props:
73+
74+
```jsx title="index.jsx"
75+
import { Insight } from "./Insight";
76+
77+
export const onTransaction = ({ transaction }) => {
78+
return { content: <Insight from={transaction.from} to={transaction.to} /> };
79+
};
80+
```
81+
82+
You can also access props using destructuring.
83+
This is not specific to JSX, but a feature of JavaScript:
84+
85+
```jsx title="Insight.jsx"
86+
export const Insight = ({ from, to }) => {
87+
return (
88+
<Box>
89+
<Row label="From">
90+
<Address address={from} />
91+
</Row>
92+
<Row label="To">
93+
{to ? <Address address={to} /> : <Text>None</Text>}
94+
</Row>
95+
</Box>
96+
);
97+
};
98+
```
99+
100+
## Return multiple elements
101+
102+
A JSX expression can only contain a single root element.
103+
To return multiple elements, wrap them in a parent element, such as `Box`.
104+
In the previous example, the two `Row` elements are wrapped in a `Box` element.
105+
Trying to return multiple elements without a parent element results in a syntax error:
106+
107+
```jsx title="WRONG-Insight.jsx"
108+
export const Insight = ({ from, to }) => {
109+
110+
// This causes a syntax error
111+
return (
112+
<Row label="From">
113+
<Address address={from} />
114+
</Row>
115+
<Row label="To">
116+
{to ? <Address address={to} /> : <Text>None</Text>}
117+
</Row>
118+
);
119+
};
120+
```
121+
122+
## Return a list
123+
124+
To return a list of elements, you can use an array.
125+
In the following example, the `Accounts` components receives an array of accounts as props, and uses
126+
the array to display a list of accounts using `Array.map`:
127+
128+
```jsx title="Accounts.jsx"
129+
export const Accounts = ({ accounts }) => {
130+
return (
131+
<Box>
132+
<Heading>Accounts</Heading>
133+
{accounts.map((account) => (
134+
<Row label={account.name}>
135+
<Address address={account.address} />
136+
</Row>
137+
))}
138+
</Box>
139+
);
140+
};
141+
```
142+
143+
To use the `Accounts` component, you can pass an array of accounts as props:
144+
145+
```jsx title="index.jsx"
146+
import { Accounts } from "./Accounts";
147+
148+
export const onHomepage = () => {
149+
const accounts = [
150+
{
151+
name: "Account 1",
152+
address: "0x6827b8f6cc60497d9bf5210d602C0EcaFDF7C405"
153+
},
154+
{
155+
name: "Account 2",
156+
address: "0x71C7656EC7ab88b098defB751B7401B5f6d8976F"
157+
}
158+
];
159+
return <Accounts accounts={accounts} />;
160+
};
161+
```
162+
163+
## Spread props
164+
165+
If an object has the same keys and value types as the props of a component, you can spread the
166+
object's properties as props for the component.
167+
For example, given the following component:
168+
169+
```jsx title="Account.jsx"
170+
export const Account = ({ name, address }) => {
171+
return <Row label={name}>
172+
<Address address={address} />
173+
</Row>
174+
};
175+
```
176+
177+
Instead of writing:
178+
179+
```jsx title="index.jsx"
180+
const myAccount = {
181+
name: "Account 1",
182+
address: "0x6827b8f6cc60497d9bf5210d602C0EcaFDF7C405"
183+
};
184+
185+
// ...
186+
return <Account name={myAccount.name} address={myAccount.address} />
187+
```
188+
189+
You can write:
190+
191+
```jsx
192+
return <Account {...myAccount} />
193+
```
194+
195+
## Use with TypeScript
196+
197+
The `@metamask/snaps-sdk/jsx` package exports a `SnapComponent` type that you can use to define
198+
components that are compatible with TypeScript.
199+
The `SnapComponent` type is generic: it accepts a `Props` type parameter that defines the shape of
200+
the props object.
201+
For example:
202+
203+
```tsx title="Insight.tsx"
204+
import type { SnapComponent } from "@metamask/snaps-sdk/jsx";
205+
import { Button, Box, Text, Row, Address } from "@metamask/snaps-sdk/jsx";
206+
207+
type InsightProps = {
208+
from: string;
209+
to?: string;
210+
};
211+
212+
export const Insight: SnapComponent<InsightProps> = ({ from, to }) => {
213+
return (
214+
<Box>
215+
<Row label="From">
216+
<Address address={from as `0x${string}`} />
217+
</Row>
218+
<Row label="To">
219+
{to ? <Address address={to as `0x${string}`} /> : <Text>None</Text>}
220+
</Row>
221+
</Box>
222+
);
223+
};
224+
```
225+
226+
Use the following steps to create user-defined components with TypeScript:
227+
228+
1. Import the `SnapComponent` type:
229+
230+
```tsx
231+
import type { SnapComponent } from "@metamask/snaps-sdk/jsx";
232+
```
233+
234+
2. Define a type for the props of your component.
235+
For example:
236+
237+
```tsx
238+
type InsightProps = {
239+
from: string;
240+
to?: string;
241+
};
242+
```
243+
244+
3. Annotate the type of your component.
245+
For example:
246+
247+
```tsx
248+
export const Insight: SnapComponent<InsightProps> = ({ from, to }) => {
249+
// ...
250+
};
251+
```
252+
253+
This has two effects:
254+
255+
- It allows TypeScript to infer the types of the props inside your component.
256+
- It ensures that the props passed to the component match the expected props.
257+
In this example, using the `Insight` component without the `from` prop, or passing a `number`
258+
instead of a `string` for the `from` prop results in a type error.

snaps/features/custom-ui/with-jsx.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,7 @@ module.exports.onHomePage = async () => {
712712
<img src={require("../../assets/custom-ui-heading.png").default} alt="Text UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
713713
</p>
714714

715-
## Emojis
715+
### Emojis
716716

717717
Text-based components (such as [`Heading`](#heading) and [`Text`](#text)) accept emojis.
718718

@@ -738,3 +738,7 @@ await snap.request({
738738
<p align="center">
739739
<img src={require("../../assets/custom-ui-emojis.png").default} alt="Emojis UI example" width="450px" style={{border: "1px solid #DCDCDC"}} />
740740
</p>
741+
742+
## User-defined components
743+
744+
In addition to the components provided by the SDK, you can [define your own components](user-defined-components.md).

0 commit comments

Comments
 (0)