{"id":20039544,"url":"https://github.com/yishn/wasm-react","last_synced_at":"2025-04-04T13:06:12.214Z","repository":{"id":37059865,"uuid":"487667563","full_name":"yishn/wasm-react","owner":"yishn","description":"WASM bindings for React.","archived":false,"fork":false,"pushed_at":"2025-02-01T19:18:11.000Z","size":459,"stargazers_count":92,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-28T12:04:15.807Z","etag":null,"topics":["javascript","js","react","rust","rust-wasm","ui","wasm","wasm-bindgen","webassembly"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yishn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-01T23:45:30.000Z","updated_at":"2025-03-14T08:28:10.000Z","dependencies_parsed_at":"2025-02-24T04:00:33.535Z","dependency_job_id":"04e53c47-6dea-4918-ac3a-610f46a6434e","html_url":"https://github.com/yishn/wasm-react","commit_stats":{"total_commits":282,"total_committers":1,"mean_commits":282.0,"dds":0.0,"last_synced_commit":"8072e06627fd6072558c4cfd6a23df10b7b52752"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yishn%2Fwasm-react","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yishn%2Fwasm-react/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yishn%2Fwasm-react/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yishn%2Fwasm-react/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yishn","download_url":"https://codeload.github.com/yishn/wasm-react/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247178097,"owners_count":20896784,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","js","react","rust","rust-wasm","ui","wasm","wasm-bindgen","webassembly"],"created_at":"2024-11-13T10:38:01.370Z","updated_at":"2025-04-04T13:06:12.174Z","avatar_url":"https://github.com/yishn.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# wasm-react 🦀⚛️\n\n[![GitHub](https://img.shields.io/badge/GitHub-Repo-lightgrey?logo=github)](https://github.com/yishn/wasm-react)\n[![crates.io](https://img.shields.io/crates/v/wasm-react)](https://crates.io/crates/wasm-react)\n[![CI](https://github.com/yishn/wasm-react/actions/workflows/ci.yml/badge.svg)](https://github.com/yishn/wasm-react/actions/workflows/ci.yml)\n[![docs.rs](https://img.shields.io/docsrs/wasm-react)](https://docs.rs/wasm-react/)\n\nWASM bindings for [React].\n\n## Introduction\n\nThis library enables you to write and use React components in Rust, which then\ncan be exported to JS to be reused or rendered.\n\n### Why React?\n\nReact is one of the most popular UI framework for JS with a thriving community\nand lots of libraries written for it. Standing on the shoulder of giants, you\nwill be able to write complex frontend applications with Rust.\n\n### Goals\n\n- Provide Rust bindings for the public API of `react` as close to the original\n  API as possible, but with Rust in mind.\n- Provide an ergonomic way to write components.\n- Provide ways to interact with components written in JS.\n\n### Non-Goals\n\n- Provide bindings for any other library than `react`, e.g. `react-dom`.\n- Reimplementation of the reconciliation algorithm or runtime.\n- Emphasis on performance.\n\n## Getting Started\n\nMake sure you have Rust and Cargo installed. You can install `wasm-react` with\ncargo. Furthermore, if you want to expose your Rust components to JS, you also\nneed `wasm-bindgen` and have [`wasm-pack`] installed.\n\n```sh\n$ cargo add wasm-react\n$ cargo add wasm-bindgen@0.2\n```\n\n### Creating a Component\n\nFirst, you need to define a struct for the props of your component. To define\nthe render function, you need to implement the trait `Component` for your\nstruct:\n\n```rust\nuse wasm_react::{h, Component, VNode};\n\nstruct Counter {\n  counter: i32,\n}\n\nimpl Component for Counter {\n  fn render(\u0026self) -\u003e VNode {\n    h!(div)\n      .build((\n        h!(p).build((\"Counter: \", self.counter)),\n        h!(button).build(\"Increment\"),\n      ))\n  }\n}\n```\n\n### Add State\n\nYou can use the `use_state()` hook to make your component stateful:\n\n```rust\nuse wasm_react::{h, Component, VNode};\nuse wasm_react::hooks::use_state;\n\nstruct Counter {\n  initial_counter: i32,\n}\n\nimpl Component for Counter {\n  fn render(\u0026self) -\u003e VNode {\n    let counter = use_state(|| self.initial_counter);\n\n    let result = h!(div)\n      .build((\n        h!(p).build((\"Counter: \", *counter.value())),\n        h!(button).build(\"Increment\"),\n      ));\n    result\n  }\n}\n```\n\nNote that according to the usual Rust rules, the state will be dropped when the\nrender function returns. `use_state()` will prevent that by tying the lifetime\nof the state to the lifetime of the component, therefore _persisting_ the state\nthrough the entire lifetime of the component.\n\n### Add Event Handlers\n\nTo create an event handler, you pass a `Callback` created from a Rust closure.\nYou can use the helper macro `clones!` to clone-capture the environment more\nergonomically.\n\n```rust\nuse wasm_react::{h, clones, Component, Callback, VNode};\nuse wasm_react::hooks::{use_state, Deps};\n\nstruct Counter {\n  initial_counter: i32,\n}\n\nimpl Component for Counter {\n  fn render(\u0026self) -\u003e VNode {\n    let message = use_state(|| \"Hello World!\");\n    let counter = use_state(|| self.initial_counter);\n\n    let result = h!(div)\n      .build((\n        h!(p).build((\"Counter: \", *counter.value())),\n\n        h!(button)\n          .on_click(\u0026Callback::new({\n            clones!(message, mut counter);\n\n            move |_| {\n              println!(\"{}\", message.value());\n              counter.set(|c| c + 1);\n            }\n          }))\n          .build(\"Increment\"),\n\n        h!(button)\n          .on_click(\u0026Callback::new({\n            clones!(mut counter);\n\n            move |_| counter.set(|c| c - 1)\n          }))\n          .build(\"Decrement\"),\n      ));\n    result\n  }\n}\n```\n\n### Export Components for JS Consumption\n\nFirst, you'll need [`wasm-pack`]. You can use `export_components!` to export\nyour Rust component for JS consumption. Requirement is that your component\nimplements `TryFrom\u003cJsValue, Error = JsValue\u003e`.\n\n```rust\nuse wasm_react::{h, export_components, Component, VNode};\nuse wasm_bindgen::JsValue;\n\nstruct Counter {\n  initial_counter: i32,\n}\n\nimpl Component for Counter {\n  fn render(\u0026self) -\u003e VNode {\n    /* … */\n    VNode::new()\n  }\n}\n\nstruct App;\n\nimpl Component for App {\n  fn render(\u0026self) -\u003e VNode {\n    h!(div).build((\n      Counter {\n        initial_counter: 0,\n      }\n      .build(),\n    ))\n  }\n}\n\nimpl TryFrom\u003cJsValue\u003e for App {\n  type Error = JsValue;\n\n  fn try_from(_: JsValue) -\u003e Result\u003cSelf, Self::Error\u003e {\n    Ok(App)\n  }\n}\n\nexport_components! { App }\n```\n\nUse `wasm-pack` to compile your Rust code into WASM:\n\n```sh\n$ wasm-pack build\n```\n\nDepending on your JS project structure, you may want to specify the `--target`\noption, see\n[`wasm-pack` documentation](https://rustwasm.github.io/docs/wasm-pack/commands/build.html#target).\n\nAssuming you use a bundler that supports JSX and WASM imports in ES modules like\nWebpack, you can use:\n\n```js\nimport React from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nasync function main() {\n  const { WasmReact, App } = await import(\"./path/to/pkg/project.js\");\n  WasmReact.useReact(React); // Tell wasm-react to use your React runtime\n\n  const root = createRoot(document.getElementById(\"root\"));\n  root.render(\u003cApp /\u003e);\n}\n```\n\nIf you use plain ES modules, you can do the following:\n\n```sh\n$ wasm-pack build --target web\n```\n\n```js\nimport \"https://unpkg.com/react/umd/react.production.min.js\";\nimport \"https://unpkg.com/react-dom/umd/react-dom.production.min.js\";\nimport init, { WasmReact, App } from \"./path/to/pkg/project.js\";\n\nasync function main() {\n  await init(); // Need to load WASM first\n  WasmReact.useReact(window.React); // Tell wasm-react to use your React runtime\n\n  const root = ReactDOM.createRoot(document.getElementById(\"root\"));\n  root.render(React.createElement(App, {}));\n}\n```\n\n### Import Components for Rust Consumption\n\nYou can use `import_components!` together with `wasm-bindgen` to import JS\ncomponents for Rust consumption. First, prepare your JS component:\n\n```js\n// /.dummy/myComponents.js\nimport \"https://unpkg.com/react/umd/react.production.min.js\";\n\nexport function MyComponent(props) {\n  /* … */\n}\n```\n\nMake sure the component uses the same React runtime as specified for\n`wasm-react`. Afterwards, use `import_components!`:\n\n```rust\nuse wasm_react::{h, import_components, Component, VNode};\nuse wasm_bindgen::prelude::*;\n\nimport_components! {\n  #[wasm_bindgen(module = \"/.dummy/myComponents.js\")]\n\n  MyComponent\n}\n\nstruct App;\n\nimpl Component for App {\n  fn render(\u0026self) -\u003e VNode {\n    h!(div).build((\n      MyComponent::new()\n        .attr(\"prop\", \u0026\"Hello World!\".into())\n        .build(()),\n    ))\n  }\n}\n```\n\n### Passing Down Non-Copy Props\n\nSay you define a component with the following struct:\n\n```rust\nuse std::rc::Rc;\n\nstruct TaskList {\n  tasks: Vec\u003cRc\u003cstr\u003e\u003e\n}\n```\n\nYou want to include `TaskList` in a container component `App` where `tasks` is\nmanaged by a state:\n\n```rust\nuse std::rc::Rc;\nuse wasm_react::{h, Component, VNode};\nuse wasm_react::hooks::{use_state, State};\n\nstruct TaskList {\n  tasks: Vec\u003cRc\u003cstr\u003e\u003e\n}\n\nimpl Component for TaskList {\n  fn render(\u0026self) -\u003e VNode {\n    /* … */\n    VNode::default()\n  }\n}\n\nstruct App;\n\nimpl Component for App {\n  fn render(\u0026self) -\u003e VNode {\n    let tasks: State\u003cVec\u003cRc\u003cstr\u003e\u003e\u003e = use_state(|| vec![]);\n\n    h!(div).build((\n      TaskList {\n        tasks: todo!(), // Oops, `tasks.value()` does not fit the type\n      }\n      .build(),\n    ))\n  }\n}\n```\n\nChanging the type of `tasks` to fit `tasks.value()` doesn't work, since\n`tasks.value()` returns a non-`'static` reference while component structs can\nonly contain `'static` values. You can clone the underlying `Vec`, but this\nintroduces unnecessary overhead. In this situation you might think you can\nsimply change the type of `TaskList` to a `State`:\n\n```rust\nuse std::rc::Rc;\nuse wasm_react::{h, Component, VNode};\nuse wasm_react::hooks::{use_state, State};\n\nstruct TaskList {\n  tasks: State\u003cVec\u003cRc\u003cstr\u003e\u003e\u003e\n}\n```\n\nThis works as long as the prop `tasks` is guaranteed to come from a state. But\nthis assumption may not hold. You might want to pass on `Rc\u003cVec\u003cRc\u003cstr\u003e\u003e\u003e` or\n`Memo\u003cVec\u003cRc\u003cstr\u003e\u003e\u003e` instead in the future or somewhere else. To be as generic\nas possible, you can use `PropContainer`:\n\n```rust\nuse std::rc::Rc;\nuse wasm_react::{h, Component, PropContainer, VNode};\nuse wasm_react::hooks::{use_state, State};\n\nstruct TaskList {\n  tasks: PropContainer\u003cVec\u003cRc\u003cstr\u003e\u003e\u003e\n}\n\nimpl Component for TaskList {\n  fn render(\u0026self) -\u003e VNode {\n    /* Do something with `self.tasks.value()`… */\n    VNode::default()\n  }\n}\n\nstruct App;\n\nimpl Component for App {\n  fn render(\u0026self) -\u003e VNode {\n    let tasks: State\u003cVec\u003cRc\u003cstr\u003e\u003e\u003e = use_state(|| vec![]);\n\n    h!(div).build((\n      TaskList {\n        // Cloning `State` has low cost as opposed to cloning the underlying\n        // `Vec`.\n        tasks: tasks.clone().into(),\n      }\n      .build(),\n    ))\n  }\n}\n```\n\n## Known Caveats\n\n- Rust components cannot be part of the subtree of a `StrictMode` component.\n\n  wasm-react uses React hooks to manually manage Rust memory. `StrictMode` will\n  run hooks and their destructors twice which will result in a double free.\n\n## License\n\nLicensed under either of\n\n- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or\n  \u003chttps://www.apache.org/licenses/LICENSE-2.0\u003e)\n- MIT license ([LICENSE-MIT](LICENSE-MIT) or\n  \u003chttps://opensource.org/licenses/MIT\u003e)\n\nat your option.\n\n## Contribution\n\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in the work by you, as defined in the Apache-2.0 license, shall be\ndual licensed as above, without any additional terms or conditions.\n\n[react]: https://react.dev\n[`wasm-pack`]: https://rustwasm.github.io/wasm-pack/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyishn%2Fwasm-react","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyishn%2Fwasm-react","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyishn%2Fwasm-react/lists"}