Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ target/
node_modules

dist

26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ This is an experimental hobby project exploring MoonBit bindings for React. The
- `use_state[T](initial: T) -> (T, (T) -> Unit)` - State management hook
- `use_reducer[S: Default, A](initial?: S, reducer: (S, A) -> S) -> (S, (A) -> Unit)` - Reducer hook
- `use_effect_once(effect: () -> Unit) -> Unit` - Effect hook that runs only once
- `use_effect_deps(effect: () -> Unit, deps: Array[JsValue]) -> Unit` - Effect hook with dependencies
- `use_layout_effect_deps(effect: () -> Unit, deps: Array[JsValue]) -> Unit` - Layout effect hook
- `use_memo_deps[A](factory: () -> A, deps: Array[JsValue]) -> A` - Memoization hook
- `use_callback_deps[F](callback: F, deps: Array[JsValue]) -> F` - Callback memoization hook
- `use_callback0_deps(f: () -> Unit, deps: Array[JsValue]) -> () -> Unit` - Zero-argument callback hook
- `use_effect_deps(effect: () -> Unit, deps: Array[JsObscure]) -> Unit` - Effect hook with dependencies
- `use_layout_effect_deps(effect: () -> Unit, deps: Array[JsObscure]) -> Unit` - Layout effect hook
- `use_memo_deps[A](factory: () -> A, deps: Array[JsObscure]) -> A` - Memoization hook
- `use_callback_deps[F](callback: F, deps: Array[JsObscure]) -> F` - Callback memoization hook
- `use_callback0_deps(f: () -> Unit, deps: Array[JsObscure]) -> () -> Unit` - Zero-argument callback hook
- `use_ref[T: JsValueTrait](initial: T) -> ReactRef[T]` - Reference hook
- `obscure[T](v: T) -> JsValue` - Dependency conversion helper function
- `obscure[T](v: T) -> JsObscure` - Dependency conversion helper function

### HTML Element Bindings

Expand Down Expand Up @@ -61,19 +61,29 @@ This is an experimental hobby project exploring MoonBit bindings for React. The

## Quick Start

Before writing any MoonBit code, make sure to include the React bindings in your project.

```js
import * as React from "react";
import * as ReactDOMClient from "react-dom/client";

window.React = React;
window.ReactDOMClient = ReactDOMClient;
```

Here's a simple example of how to use this library:

```moonbit
// Define your component props
struct ContainerProps {} derive(Default)

// Implement JsValueTrait for props
impl @react.JsValueTrait for ContainerProps with to_value(_self) -> @react.JsValue {
impl @react.JsValueTrait for ContainerProps with to_value(_self) -> @dom.JsObscure {
@react.JsObject::new().to_value()
}

impl @react.JsValueTrait for ContainerProps with from_value(
_value : @react.JsValue,
_value : @dom.JsObscure,
) -> ContainerProps {
ContainerProps::default()
}
Expand Down
1 change: 0 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TodoMVC - MoonBit + React</title>
<link rel="stylesheet" href="./styles/todomvc.css">
</head>
<body>
<div id="root"></div>
Expand Down
4 changes: 2 additions & 2 deletions moon.mod.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "tiye/react",
"version": "0.0.2-a3",
"version": "0.0.3",
"deps": {
"tiye/dom-ffi": "0.1.1",
"tiye/dom-ffi": "0.1.2",
"tiye/respo_css": "0.1.2"
},
"readme": "README.md",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"dependencies": {
"react": "^19.1.1",
"react-dom": "^19.1.1"
"react-dom": "^19.1.1",
"todomvc-app-css": "^2.4.3"
}
}
123 changes: 59 additions & 64 deletions src/hooks.mbt
Original file line number Diff line number Diff line change
@@ -1,137 +1,129 @@
///|
// React Hooks wrappers, aligned with existing use_state style

typealias @dom.JsObscure

///|
extern "js" fn react_use_effect(effect : JsValue, deps : JsValue) -> Unit =
extern "js" fn react_use_effect(
effect : @dom.JsObscure,
deps : Array[@dom.JsObscure],
) -> Unit =
#| (effect, deps) => window.React.useEffect(effect, deps)

///|
extern "js" fn react_use_layout_effect(
effect : JsValue,
deps : JsValue,
effect : @dom.JsObscure,
deps : Array[@dom.JsObscure],
) -> Unit =
#| (effect, deps) => window.React.useLayoutEffect(effect, deps)

///|
extern "js" fn react_use_memo(factory : JsValue, deps : JsValue) -> JsValue =
extern "js" fn react_use_memo(
factory : @dom.JsObscure,
deps : Array[@dom.JsObscure],
) -> @dom.JsObscure =
#| (factory, deps) => window.React.useMemo(factory, deps)

///|
extern "js" fn react_use_callback(
callback : JsValue,
deps : JsValue,
) -> JsValue =
callback : @dom.JsObscure,
deps : Array[@dom.JsObscure],
) -> JsObscure =
#| (callback, deps) => window.React.useCallback(callback, deps)

///|
extern "js" fn react_use_ref(initial : JsValue) -> JsValue =
extern "js" fn react_use_ref(initial : @dom.JsObscure) -> @dom.JsObscure =
#| (initial) => window.React.useRef(initial)

///|
extern "js" fn react_ref_get_value(r : JsValue) -> JsValue =
extern "js" fn react_ref_get_value(r : @dom.JsObscure) -> @dom.JsObscure =
#| (ref) => ref.current

///|
extern "js" fn react_ref_set_value(r : JsValue, value : JsValue) -> Unit =
extern "js" fn react_ref_set_value(
r : @dom.JsObscure,
value : JsObscure,
) -> Unit =
#| (ref, value) => ref.current = value

///|
extern "js" fn react_use_reducer(
reducer : JsValue,
initial : JsValue,
) -> JsValue =
reducer : JsObscure,
initial : JsObscure,
) -> JsObscure =
#| (reducer, initial) => window.React.useReducer(reducer, initial)

///|
extern "js" fn fn0_from_value(v : JsValue) -> () -> Unit =
#| (v) => v
fn fn0_to_js_obscure(f : () -> Unit) -> @dom.JsObscure = "%identity"

///|
extern "js" fn fn1_from_value(v : JsValue) -> (JsValue) -> Unit =
#| (v) => v

///|
/// 最多支持 8 个依赖项
pub(all) enum Deps[A, B, C, D, E, F, G, H] {
Dep0
Dep1(A)
Dep2(A, B)
Dep3(A, B, C)
Dep4(A, B, C, D)
Dep5(A, B, C, D, E)
Dep6(A, B, C, D, E, F)
Dep7(A, B, C, D, E, F, G)
Dep8(A, B, C, D, E, F, G, H)
}

///|
fn deps_to_js_array(deps : Array[JsValue]) -> JsValue {
let arr = JsArray::new()
for d in deps {
arr.push(d)
}
arr.to_value()
}
fn fn1_from_js_obscure(v : JsObscure) -> (JsObscure) -> Unit = "%identity"

///|
// useLayoutEffect
pub fn use_layout_effect_deps(
effect : () -> Unit,
deps : Array[JsValue],
deps : Array[@dom.JsObscure],
) -> Unit {
react_use_layout_effect(any_to_js_value(effect), deps_to_js_array(deps))
react_use_layout_effect(fn0_to_js_obscure(effect), deps)
}

///|
// useMemo
pub fn[A] use_memo_deps(factory : () -> A, deps : Array[JsValue]) -> A {
let v = react_use_memo(any_to_js_value(factory), deps_to_js_array(deps))
any_from_js_value(v)
pub fn[A] use_memo_deps(factory : () -> A, deps : Array[@dom.JsObscure]) -> A {
let v = react_use_memo(@dom.v_to_js_obscure(factory), deps)
@dom.js_obscure_to_v(v)
}

///|
// useCallback
pub fn[F] use_callback_deps(callback : F, deps : Array[JsValue]) -> F {
let v = react_use_callback(any_to_js_value(callback), deps_to_js_array(deps))
any_from_js_value(v)
pub fn[F] use_callback_deps(callback : F, deps : Array[@dom.JsObscure]) -> F {
let v = react_use_callback(@dom.v_to_js_obscure(callback), deps)
@dom.js_obscure_to_v(v)
}

///|
// useEffect
pub fn use_effect_once(effect : () -> Unit) -> Unit {
react_use_effect(any_to_js_value(effect), JsArray::new().to_value())
react_use_effect(fn0_to_js_obscure(effect), [])
}

///|
/// a short hand to turn value into JsValue in hook deps
pub fn[T] obscure(v : T) -> JsValue = "%identity"
/// a short hand to turn value into JsObscure in hook deps
pub fn[T] obscure(v : T) -> @dom.JsObscure = "%identity"

///|
pub fn use_effect_deps(effect : () -> Unit, deps : Array[JsValue]) -> Unit {
react_use_effect(any_to_js_value(effect), deps_to_js_array(deps))
pub fn use_effect_deps(
effect : () -> Unit,
deps : Array[@dom.JsObscure],
) -> Unit {
react_use_effect(fn0_to_js_obscure(effect), deps)
}

///|
// useCallback (no argument)
pub fn use_callback0_deps(f : () -> Unit, deps : Array[JsValue]) -> () -> Unit {
let raw = react_use_callback(any_to_js_value(f), deps_to_js_array(deps))
fn0_from_value(raw)
pub fn use_callback0_deps(
f : () -> Unit,
deps : Array[@dom.JsObscure],
) -> () -> Unit {
let raw = react_use_callback(@dom.v_to_js_obscure(f), deps)
@dom.js_obscure_to_v(raw)
}

///|
struct ReactRef[T] {
js_value : JsValue
js_value : @dom.JsObscure
mut _v0 : T
}

///|
pub fn[T] ReactRef::from(v : T) -> ReactRef[T] {
ReactRef::{ js_value: react_use_ref(any_to_js_value(v)), _v0: v }
ReactRef::{ js_value: react_use_ref(@dom.v_to_js_obscure(v)), _v0: v }
}

///|
pub fn[T] ReactRef::get(self : ReactRef[T]) -> T {
any_from_js_value(react_ref_get_value(self.js_value))
@dom.js_obscure_to_v(react_ref_get_value(self.js_value))
}

///|
Expand All @@ -142,8 +134,11 @@ pub fn[T] ReactRef::set(self : ReactRef[T], value : T) -> Unit {

///|
// useRef
pub fn[T : JsValueTrait] use_ref(initial : T) -> ReactRef[T] {
ReactRef::{ js_value: react_use_ref(initial.to_value()), _v0: initial }
pub fn[T] use_ref(initial : T) -> ReactRef[T] {
ReactRef::{
js_value: react_use_ref(@dom.v_to_js_obscure(initial)),
_v0: initial,
}
}

///|
Expand All @@ -155,8 +150,8 @@ pub fn[S : Default, A] use_reducer(
let pair = react_use_reducer(
any_to_js_value(reducer),
any_to_js_value(initial.unwrap_or_default()),
).to_array()
let s0 = any_from_js_value(pair[0])
let dispatch_raw = fn1_from_value(pair[1])
)
let s0 = any_from_js_value(pair.get("0"))
let dispatch_raw = fn1_from_js_obscure(pair.get("1"))
(s0, fn(a : A) { dispatch_raw(any_to_js_value(a)) })
}
73 changes: 0 additions & 73 deletions src/js-value-trait.mbt

This file was deleted.

Loading