Implement React v18 from Scratch Using WASM and Rust - [11] Implement Event System
Tags: react, webassembly, rust
Published: 2024-05-07 07:31:52

> Based on [big-react](https://github.com/BetaSu/big-react)，I am going to implement React v18 core features from scratch using WASM and Rust.
>
> Code Repository：https://github.com/ParadeTo/big-react-wasm
>
> The tag related to this article：[v11](https://github.com/ParadeTo/big-react-wasm/tree/v11)

Without an event system, React lacks interactivity, and users are unable to interact with the application. Therefore, in this article, we will use the `click` event as an example to demonstrate how to implement it.

Let's take a look at how the official version of React handles events, using the following code as an example.

```js
const App = () => {
  innerClick = () => {
    console.log('A: react inner click.')
  }

  outerClick = () => {
    console.log('B: react outer click.')
  }

  return (
    <div id='outer' onClickCapture={this.outerClick}>
      <button id='inner' onClick={this.innerClick}>
        button
      </button>
    </div>
  )
}
```

When an event is triggered on the root element, we can obtain the native event object `NativeEvent`. By accessing the `target` property, we can retrieve the current clicked element, which in this case is the `button`. Using the property `__reactFiber$*****` (where `*` represents a random number) on the button, we can access the corresponding `FiberNode`. Additionally, React utilizes the `NativeEvent` to generate a `SyntheticEvent`, which has several important properties worth noting:

- `nativeEvent`: Points to the `NativeEvent`.
- `_dispatchListeners`: Stores the event listener functions to be executed.
- `_dispatchInstances`: Stores the `FiberNode` objects to which the event listener functions belong.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u9y1cogt98gltiv062k3.png)

Next, we will collect the event listener functions to be executed in both the capturing and bubbling phases.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2wqt9j7qc7kaxh315lda.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ui91o8o4ryzgcjxhgwkx.png)


Finally, the methods in `_dispatchListeners` are executed in order, and the corresponding `stateNode` is obtained from the `FiberNode` in `_dispatchInstances` as the `currentTarget` on the `SyntheticEvent`.

The `SyntheticEvent` also has a `stopPropagation` method. Once called, the methods following `_dispatchListeners` will not be executed, effectively preventing event propagation.

That concludes the introduction to the React event system. For more information, you can refer to [this article](https://www.paradeto.com/2020/08/20/react-event/).

However, the implementation of Big React differs from the official React. Here's how it's done:

During the complete work phase, when creating the `stateNode` for a `FiberNode` node, the event listener functions on the `FiberNode` node are copied to the `Element`.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ogias2omu8alxke8bj08.png)

When an event is triggered, the event listener functions are collected by traversing up the tree using the `target` property on the `NativeEvent`. If it's an `onClick` event, the function is `pushed` into the `bubble` list. If it's an `onClickCapture` event, the function is `inserted` into the `capture` list.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/148htfj3lm9or7tcd5e7.png)

Then, the event listener functions in the `capture` list are executed from start to finish, followed by the event listener functions in the `bubble` list.

How is event propagation prevented? The answer lies in modifying the method on the `NativeEvent`.

```rust
fn create_synthetic_event(e: Event) -> Event {
    Reflect::set(&*e, &"__stopPropagation".into(), &JsValue::from_bool(false));

    let e_cloned = e.clone();
    let origin_stop_propagation = derive_from_js_value(&*e, "stopPropagation");
    let closure = Closure::wrap(Box::new(move || {
        // set __stopPropagation to true
        Reflect::set(
            &*e_cloned,
            &"__stopPropagation".into(),
            &JsValue::from_bool(true),
        );
        if origin_stop_propagation.is_function() {
            let origin_stop_propagation = origin_stop_propagation.dyn_ref::<Function>().unwrap();
            origin_stop_propagation.call0(&JsValue::null());
        }
    }) as Box<dyn Fn()>);
    let function = closure.as_ref().unchecked_ref::<Function>().clone();
    closure.forget();
    Reflect::set(&*e.clone(), &"stopPropagation".into(), &function.into());
    e
}

fn trigger_event_flow(paths: Vec<Function>, se: &Event) {
    for callback in paths {
        callback.call1(&JsValue::null(), se);
        // If __stopPropagation is true, break
        if derive_from_js_value(se, "__stopPropagation")
            .as_bool()
            .unwrap()
        {
            break;
        }
    }
}
```

However, there is another issue here. The `currentTarget` is not corrected as it is in the official version of React. In this case, the `currentTarget` always refers to the root element because the event listener functions are bound to that element.

```rust
pub fn init_event(container: JsValue, event_type: String) {
  ...
  let element = container
      .clone()
      .dyn_into::<Element>()
      .expect("container is not element");
  let on_click = EventListener::new(&element.clone(), event_type.clone(), move |event| {
      dispatch_event(&element, event_type.clone(), event)
  });
  on_click.forget();
}
```

This update can be found in detail [here](https://github.com/ParadeTo/big-react-wasm/pull/10).

*Please kindly give me a star*