WASM bindings for React - enables you to write and use React components in Rust

Overview

wasm-react πŸ¦€ βš›οΈ

GitHub crates.io CI docs.rs

WASM bindings for React.

Introduction

This library enables you to write and use React components in Rust, which then can be exported to JS to be reused or rendered.

Why React?

React is one of the most popular UI framework for JS with a thriving community and lots of libraries written for it. Standing on the shoulder of giants, you will be able to write complex frontend applications with Rust.

Goals

  • Provide Rust bindings for the public API of react as close to the original API as possible, but with Rust in mind.
  • Provide an ergonomic way to write components.
  • Provide ways to interact with components written in JS.

Non-Goals

  • Provide bindings for any other library than react, e.g. react-dom.
  • Provide a reimplementation of the reconciliation algorithm or another runtime.
  • Emphasis on performance.

Getting Started

Make sure you have Rust and Cargo installed. You can include wasm-react by adding it to your Cargo.toml. Furthermore, if you want to expose your Rust components to JS, you also need wasm-bindgen and install wasm-pack.

[dependencies]
wasm-react = "0.2"
wasm-bindgen = "0.2"

Creating a Component

First, you need to define a struct for the props of your component. To define the render function, you need to implement the trait Component for your struct:

use wasm_react::{h, c, Component, VNode};

struct Counter {
  counter: i32,
}

impl Component for Counter {
  fn render(&self) -> VNode {
    h!(div)
      .build(c![
        h!(p).build(c!["Counter: ", self.counter]),
        h!(button).build(c!["Increment"]),
      ])
  }
}

Add State

You can use the use_state() hook to make your component stateful:

use wasm_react::{h, c, Component, VNode};
use wasm_react::hooks::use_state;

struct Counter {
  initial_counter: i32,
}

impl Component for Counter {
  fn render(&self) -> VNode {
    let counter = use_state(|| self.initial_counter);

    h!(div)
      .build(c![
        h!(p).build(c!["Counter: ", *counter.value()]),
        h!(button).build(c!["Increment"]),
      ])
  }
}

Note that according to the usual Rust rules, the state will be dropped when the render function returns. use_state() will prevent that by tying the lifetime of the state to the lifetime of the component, therefore persisting the state through the entire lifetime of the component.

Add Event Handlers

To create an event handler, you have to keep the lifetime of the closure beyond the render function as well, so JS can call it in the future. You can persist a closure by using the use_callback() hook:

use wasm_react::{h, c, Component, VNode};
use wasm_react::hooks::{use_state, use_callback, Deps};

struct Counter {
  initial_counter: i32,
}

impl Component for Counter {
  fn render(&self) -> VNode {
    let counter = use_state(|| self.initial_counter);
    let handle_click = use_callback({
      let mut counter = counter.clone();

      move |_| counter.set(|c| c + 1)
    }, Deps::none());

    h!(div)
      .build(c![
        h!(p).build(c!["Counter: ", *counter.value()]),
        h!(button)
          .on_click(&handle_click)
          .build(c!["Increment"]),
      ])
  }
}

Export Components for JS Consumption

First, you'll need wasm-pack. You can use export_components! to export your Rust component for JS consumption. Requirement is that your component implements TryFrom.

use wasm_react::{h, c, export_components, Component, VNode};
use wasm_bindgen::JsValue;

struct Counter {
  initial_counter: i32,
}

impl Component for Counter {
  fn render(&self) -> VNode {
    /* … */
    VNode::empty()
  }
}

struct App;

impl Component for App {
  fn render(&self) -> VNode {
    h!(div).build(c![
      Counter {
        initial_counter: 0,
      }
      .build(),
    ])
  }
}

impl TryFrom for App {
  type Error = JsValue;

  fn try_from(_: JsValue) -> Result<Self, Self::Error> {
    Ok(App)
  }
}

export_components! { App }

Use wasm-pack to compile your Rust code into WASM:

$ wasm-pack build

Depending on your JS project structure, you may want to specify the --target option, see wasm-pack documentation.

Assuming you use a bundler that supports JSX and WASM imports in ES modules like Webpack, you can use:

); }">
import React from "react";
import { createRoot } from "react-dom/client";

async function main() {
  const { WasmReact, App } = await import("./path/to/pkg/project.js");
  WasmReact.useReact(React); // Tell wasm-react to use your React runtime

  const root = createRoot(document.getElementById("root"));
  root.render(<App />);
}

If you use plain ES modules, you can do the following:

$ wasm-pack build --target web
import "https://unpkg.com/react/umd/react.production.min.js";
import "https://unpkg.com/react-dom/umd/react-dom.production.min.js";
import init, { WasmReact, App } from "./path/to/pkg/project.js";

async function main() {
  await init(); // Need to load WASM first
  WasmReact.useReact(window.React); // Tell wasm-react to use your React runtime

  const root = ReactDOM.createRoot(document.getElementById("root"));
  root.render(React.createElement(App, {}));
}

Import Components for Rust Consumption

You can use import_components! together with wasm-bindgen to import JS components for Rust consumption. First, prepare your JS component:

// /.dummy/myComponents.js
import "https://unpkg.com/react/umd/react.production.min.js";

export function MyComponent(props) {
  /* … */
}

Make sure the component uses the same React runtime as specified for wasm-react. Afterwards, use import_components!:

VNode { h!(div).build(c![ MyComponent::new() .attr("prop", &"Hello World!".into()) .build(c![]), ]) } }">
use wasm_react::{h, c, import_components, Component, VNode};
use wasm_react::props::Props;
use wasm_bindgen::prelude::*;

import_components! {
  #[wasm_bindgen(module = "/.dummy/myComponents.js")]

  MyComponent
}

struct App;

impl Component for App {
  fn render(&self) -> VNode {
    h!(div).build(c![
      MyComponent::new()
        .attr("prop", &"Hello World!".into())
        .build(c![]),
    ])
  }
}

Passing Down State as Prop

Say you define a component with the following struct:

use std::rc::Rc;

struct TaskList {
  tasks: Vec<Rc<str>>
}

You want to include TaskList in a container component App where tasks is managed by a state:

use std::rc::Rc;
use wasm_react::{h, c, Component, VNode};
use wasm_react::hooks::{use_state, State};

struct TaskList {
  tasks: Vec<Rc<str>>
}

impl Component for TaskList {
  fn render(&self) -> VNode {
    /* … */
    VNode::default()
  }
}

struct App;

impl Component for App {
  fn render(&self) -> VNode {
    let tasks: State<Vec<Rc<str>>> = use_state(|| vec![]);

    h!(div).build(c![
      TaskList {
        tasks: todo!(), // Oops, `tasks.value()` does not fit the type
      }
      .build(),
    ])
  }
}

Changing the type of tasks to fit tasks.value() doesn't work, since tasks.value() returns a non-'static reference while component structs can only contain 'static values. You can clone the underlying Vec, but this introduces unnecessary overhead. In this situation you might think you can simply change the type of TaskList to a State:

use std::rc::Rc;
use wasm_react::{h, c, Component, VNode};
use wasm_react::hooks::{use_state, State};

struct TaskList {
  tasks: State<Vec<Rc<str>>>
}

This works as long as the prop tasks is guaranteed to come from a state. But this assumption may not hold. You might want to pass on Rc>> or Memo>> instead in the future or somewhere else. To be as generic as possible, you can use ValueContainer:

use std::rc::Rc;
use wasm_react::{h, c, Component, ValueContainer, VNode};
use wasm_react::hooks::{use_state, State};

struct TaskList {
  tasks: ValueContainer<Vec<Rc<str>>>
}

impl Component for TaskList {
  fn render(&self) -> VNode {
    /* Do something with `self.tasks.value()`… */
    VNode::default()
  }
}

struct App;

impl Component for App {
  fn render(&self) -> VNode {
    let tasks: State<Vec<Rc<str>>> = use_state(|| vec![]);

    h!(div).build(c![
      TaskList {
        // Cloning `State` has low cost as opposed to cloning the underlying
        // `Vec`.
        tasks: tasks.clone().into(),
      }
      .build(),
    ])
  }
}

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

You might also like...
Rust implementation of the Mina protocol, targeting Wasm and ARM architectures.

Mina-rs An implementation of Mina protocol in Rust, with focus on web and Wasm compatibility ** As you can probably tell this is a WIP! Don't use for

Spine runtime for Rust (and wasm!) transpiled from the official C Runtime.

rusty_spine Spine runtime for Rust (and wasm!) transpiled from the official C Runtime. Supports Spine 4.1. [dependencies] rusty_spine = "0.4.0" Onlin

Facilitating high-level interactions between Wasm modules and JavaScript

wasm-bindgen Facilitating high-level interactions between Wasm modules and JavaScript. Guide | API Docs | Contributing | Chat Built with πŸ¦€ πŸ•Έ by The

Instrument and transform wasm modules.

wasm-instrument A Rust library containing a collection of wasm module instrumentations and transformations mainly useful for wasm based block chains a

TSS of GG18 by WASM, for Confidential Transaction Generation and Signing

TSS WASM portable lightweight client application for threshold ECDSA (based on GG18), built on&for multi-party-ecdsa : Wasm HW friendly Dev yarn build

Lunatic based webserver embedding WASM. Supports scaling down to zero and up to infinity.

Frenezulo A WASM-embedding webserver build on top of submillisecond and lunatic. Build to serve as an entry point for microservices compiled to WASM.

2D particle system with custom material traits. Works well in wasm webgl2 and mobile
2D particle system with custom material traits. Works well in wasm webgl2 and mobile

Bevy Enoki Enoki - A 2D particle system for the Bevy game engine. Overview The Enoki particle system is a CPU calculate particle system, that uses GPU

πŸ“¦βœ¨ your favorite rust -> wasm workflow tool!
πŸ“¦βœ¨ your favorite rust - wasm workflow tool!

πŸ“¦ ✨ wasm-pack Your favorite Rust β†’ Wasm workflow tool! Docs | Contributing | Chat Built with πŸ¦€ πŸ•Έ by The Rust and WebAssembly Working Group About Th

Gun port in rust & wasm

gun-rs-wasm Rust & WASM port of Gun. For a non-wasm version, check out gun-rs Example (source) Use npm install rusty-gun import { Node as Gun } from "

Releases(v0.3.2)
  • v0.3.2(Jul 3, 2022)

    • Breaking: ContextProvider::build() accepts children again
    • Keyed and Memoized all implement Component trait now
    • New KeyType trait allows more types to be set as key
    • Better React effect names in dev tools
    Source code(tar.gz)
    Source code(zip)
  • v0.3.0(Jun 22, 2022)

    • Breaking: More consistent way to add React key to a component: Instead of MyComponent.build_with_key(…), there's now MyComponent.key(…).build().
    • Breaking: Rewrite context handling, also fix potential UB related to it
    • Breaking: Change definition of HType trait
    • Breaking: H<HtmlTag>::style() now accepts a reference
    • Add Component::memoized() that returns a memoized version of the component
    Source code(tar.gz)
    Source code(zip)
  • v0.2.1(Jun 16, 2022)

    • Breaking: Imported components are now built with H<[MyComponent]> instead of H<ImportedComponent> therefore allowing custom convenience methods
    • Allow doc comments for exported components in export_components!
    • HType trait is not sealed anymore
    Source code(tar.gz)
    Source code(zip)
  • v0.2.0(Jun 12, 2022)

  • v0.1.1(Jun 11, 2022)

Owner
Yichuan Shen
(he/him) Software developer, mathematician at heart, wannabe artist, and Go player.
Yichuan Shen
Parametric surfaces drawn using the Rust + WASM toolchain with WebGL, React, and TypeScript.

Parametric Surfaces in the Browser My.Movie.3.mp4 Wanted to experiment with WebGL using the Rust + WASM toolchain, with React and TypeScript to glue e

Benji Nguyen 45 Oct 21, 2022
React bindings for rust

react-sys Rust bindings for React. WIP This crate is NOT ready for production and is under heavy development. This crate intends to be used by frender

null 2 May 5, 2022
Distribute a wasm SPA as HTML by wrapping it as a polyglot "html+wasm+zip"

A packer that adds a webpage to WASM module, making it self-hosted! Motivation At the moment, Browsers can not execute WebAssembly as a native single

Andreas Molzer 3 Jan 2, 2023
NPM package distributing biscuit in WebAssembly for web components

Biscuit playground This is an example application for Biscuit tokens, where you can manipulate tokens and their verification in your browser. build wi

null 0 Dec 30, 2021
Rust based WASM/JS bindings for ur-rust

ur-wasm-js WASM/JS bindings for the ur-rust rust library Getting started Installation Either build the library yourself with wasm-pack or install for

Lightning Digital Entertainment 5 Feb 28, 2024
Rust bindings for the Wasm spec interpreter.

wasm-spec-interpreter This project shows how to use ocaml-interop to call into the Wasm spec interpreter. There are several steps to making this work:

Bytecode Alliance 9 Aug 23, 2022
zzhack-cli is a Command Tool to help you quickly generate a WASM WebApp with simple configuration and zero code

English | δΈ­ζ–‡ζ–‡ζ‘£ zzhack-cli is a Command Tool that can help you quickly generate a WASM WebApp with simple configuration and zero code. It's worth menti

null 17 Feb 9, 2023
Vue, React, Solid, Angular, Svelte, and Liquid From JS Objects.

Vue, React, Solid, Angular, Svelte, and Liquid From JS Objects.

JSX One 18 Jul 17, 2021
Create a Python project automatically with rust (like create-react-app but for python)

create-python-project Create a Python project automatically with rust (like create-react-app but for python) Installation cargo install create-python-

Dhravya Shah 2 Mar 12, 2022
Easy way to write Node.js module using Rust

node-bindgen Easy way to write native Node.js module using idiomatic Rust Features Easy: Just write idiomatic Rust code, node-bindgen take care of gen

InfinyOn 346 Jan 3, 2023