React + Rust + Wasm: Introduction

Neo Quest | Nikhil Gupta

Neo Quest | Nikhil Gupta / October 21, 2022

3 min read

Summary

In this article, we will create a demo React Application that uses a Rust WASM library.

Basic React App Setup

Use create-react-app with Typescript support like so:

npx create-react-app rust-wasm-demo --template typescript

To run it, simply do the following:

cd rust-wasm-demo
npm start

Rust WASM Setup

First, install the standard Rust toolchain using the instructions here.

Next, install wasm-pack

Finally, create a Rust library like so:

cargo new rust-wasm-lib --lib

Rust Sample Code

Now, let's create a basic function in Rust that will be called by our React app.

// lib.rs

pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[test]
fn add_test() {
    assert_eq!(1 + 1, add(1, 1));
}

The second function is a unit test in Rust. You can run it natively like so:

cd rust-wasm-lib
cargo test

Next, we will create a binding that specifies how a function can be invoked from JS and its return semantics.

Export Rust Function to WASM

We will use wasm-bindgen` to export our function. To do so, let's add it to our Cargo dependencies:

cargo add wasm-bindgen

Then, let's export the function like so:

// lib.rs

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[test]
fn add_test() {
    assert_eq!(1 + 1, add(1, 1));
}

Build WASM

First, let's change the crate-type to cdylib so that it could be compiled to WASM:

# Cargo.toml

[package]
name = "rust-wasm-lib"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2.83"

Now, we will use wasm-pack installed above to build our WASM library.

wasm-pack build --target web

This will create a pkg directory with WASM and JS files.

Install WASM

Now, we can go back to the main folder and install our built WASM library:

npm i ./rust-wasm-lib/pkg

Call WASM from the demo app

Finally, let's call the exported add function from our App.ts file like so:

// App.ts

import React, { useEffect, useState } from 'react';
import logo from './logo.svg';
import init, { add } from "rust-wasm-lib";
import './App.css';

function App() {
  const [result, setResult] = useState(0);
  useEffect(() => {
    init().then(() => setResult(add(1, 2)));
  }, [setResult])

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <p>1 + 2 = {result}</p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

You should see 1 + 2 = 3 on the screen. :)