How to develop Figma plugin with React + TS
Tags: figma, react, typescript
Published: 2020-04-05 04:53:38

---
title: How to develop Figma plugin with React + TS
published: true
description: 
tags: Figma, React, TypeScript
---

## TL;DR
```
git clone https://github.com/kazuyaseki/react-figma-plugin-boilerplate.git <your project name>
yarn or npm install
yarn webpack:watch  or  npm run webpack:watch 
```

Developing Figma plugin is exciting, but when it comes to develop one with some stateful UI, it's a pain to develop in imperative way.

Thus I created a boilerplate to develop with React and here I introduce it.

The content of boilerplate is as follows.
https://github.com/kazuyaseki/react-figma-plugin-boilerplate

## How to render Figma plugin UI with React
There is nothing special, you just have to do `ReactDOM.render` to `ui.html` which is specified from `manifest.json`.

```html
<div id="app"></div>
```

```tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { App } from './App';

ReactDOM.render(<App />, document.getElementById('app'));
```

## Sample Code
I paste a sample code of plugin made with React below.

My plugin is to enable incremental search for components and create an instance from it.(actually Figma itself has this feature but I thought it would be nice when keyboard shortcut functionality is added to plugin) 

It works as the following gif(sorry my tweet is in Japanese)

{% twitter 1246423282447867904 %}

Before you start building your plugin, I recommend you to read [How Plugins Run](https://www.figma.com/plugin-docs/how-plugins-run/) document.

You have to beware that main thread which can reference Figma object is different from plugin UI thread.

So you need to use message object to pass data between the threads, my sample code has codes for both directions, so please refer to it.

```tsx
import { subscribeOnMessages } from 'react-figma';

figma.showUI(__html__);

const componentNodes = figma.root.findAll((node) => node.type === 'COMPONENT');
const conmponentsData = componentNodes.map((node) => ({
  id: node.id,
  name: node.name,
}));
figma.ui.postMessage(conmponentsData);

figma.ui.onmessage = (message) => {
  subscribeOnMessages(message);

  if (message.type === 'create-instance') {
    const component = figma.root.findOne(
      (node) => node.id === message.id
    ) as ComponentNode;
    component.createInstance();
  }
};
```

```tsx
import * as React from 'react';

type ComponentItemType = {
  id: string;
  name: string;
};

export const App = () => {
  const [query, setQuery] = React.useState('');
  const [components, setComponents] = React.useState<ComponentItemType[]>([]);

  React.useEffect(() => {
    onmessage = (event) => {
      setComponents(event.data.pluginMessage as ComponentItemType[]);
    };
  }, []);

  const create = (id: string) => {
    parent.postMessage({ pluginMessage: { type: 'create-instance', id } }, '*');
  };

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <div style={{ display: 'flex', flexDirection: 'column' }}>
        {components
          .filter((component) => {
            if (query.length === 0) {
              return true;
            }
            return component.name.includes(query);
          })
          .map((component) => (
            <button onClick={() => create(component.id)}>
              {component.name}
            </button>
          ))}
      </div>
    </div>
  );
};

```