Implement React v18 from Scratch Using WASM and Rust - [16] Implement React Noop
Tags: react, webassembly, rust
Published: 2024-06-11 04:09:40

> 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：[v16](https://github.com/ParadeTo/big-react-wasm/tree/v16)

The previous articles always mentioned the implementation of React Noop for unit testing, and today we will complete this task.

First, following the previous approach, we create a react-noop directory at the same level as react-dom:

```
├── packages
│   ├── react
│   ├── react-dom
│   ├── react-noop
│   ├── react-reconciler
│   ├── scheduler
│   └── shared
```

The project structure is similar to react-dom, but the difference lies in the implementation of `HostConfig` in react-noop. For example, in react-dom, the `create_instance` function returns an `Element` object:

```rust
fn create_instance(&self, _type: String, props: Rc<dyn Any>) -> Rc<dyn Any> {
  let window = window().expect("no global `window` exists");
  let document = window.document().expect("should have a document on window");
  match document.create_element(_type.as_ref()) {
      Ok(element) => {
          let element = update_fiber_props(
              element.clone(),
              &*props.clone().downcast::<JsValue>().unwrap(),
          );
          Rc::new(Node::from(element))
      }
      Err(_) => {
          panic!("Failed to create_instance {:?}", _type);
      }
  }
}
```

In react-noop, it returns a regular JavaScript object:

```rust
fn create_instance(&self, _type: String, props: Rc<dyn Any>) -> Rc<dyn Any> {
  let obj = Object::new();
  Reflect::set(&obj, &"id".into(), &getCounter().into());
  Reflect::set(&obj, &"type".into(), &_type.into());
  Reflect::set(&obj, &"children".into(), &**Array::new());
  Reflect::set(&obj, &"parent".into(), &JsValue::from(-1.0));
  Reflect::set(
      &obj,
      &"props".into(),
      &*props.clone().downcast::<JsValue>().unwrap(),
  );
  Rc::new(JsValue::from(obj))
}
```

Other methods are also operations on regular JavaScript objects. For more details, please refer to [this link](https://github.com/ParadeTo/big-react-wasm/pull/15).

Additionally, to facilitate testing, we need to add a method called `getChildrenAsJSX`:

```rust
impl Renderer {
    ...
    pub fn getChildrenAsJSX(&self) -> JsValue {
        let mut children = derive_from_js_value(&self.container, "children");
        if children.is_undefined() {
            children = JsValue::null();
        }
        children = child_to_jsx(children);

        if children.is_null() {
            return JsValue::null();
        }
        if children.is_array() {
            todo!("Fragment")
        }
        return children;
    }
}
```

This allows us to obtain a tree structure containing JSX objects using the `root` object. For example, the following code:

```js
const ReactNoop = require('react-noop')
const root = ReactNoop.createRoot()
root.render(
  <div>
    <p>hello</p>
    <span>world</span>
  </div>
)
setTimeout(() => {
  console.log('---------', root.getChildrenAsJSX())
}, 1000)
```

The final printed result would be:

```js
{
  $$typeof: 'react.element',
  type: 'div',
  key: null,
  ref: null,
  props: {
    children: [
      {
        $$typeof: 'react.element',
        type: 'p',
        key: null,
        ref: null,
        props: {
          children: 'hello',
        },
      },
      {
        $$typeof: 'react.element',
        type: 'span',
        key: null,
        ref: null,
        props: {
          children: 'world',
        },
      },
    ],
  },
}
```


Note that the code for printing the result is placed inside a `setTimeout` because we put the update process in a macro task while implementing Batch Update. You can refer to [this article](https://dev.to/paradeto/implement-react-v18-from-scratch-using-wasm-and-rust-13-implement-lane-and-batch-update-56ip) for more information.

Next, we include react-noop in the build script and set the build target to `nodejs` so that we can use it in a Node.js environment. However, to support JSX syntax in Node.js, we need to use Babel. Here, we can directly use `babel-node` to run our script and configure the necessary presets:

```js
// .babelrc
{
  "presets": [
    [
      "@babel/preset-react",
      {
        "development": "true"
      }
    ]
  ]
}
```


If everything goes well, the above code should run successfully in Node.js. However, when I tried to use react-noop in Jest, I encountered an error:

```js
work_loop error JsValue(RuntimeError: unreachable
    RuntimeError: unreachable
        at null.<anonymous> (wasm://wasm/00016f66:1:14042)
        ...
```

Since I couldn't solve the issue, I had to perform unit testing in Node.js instead. Here's an example test case:

```js
async function test1() {
  const arr = []

  function Parent() {
    useEffect(() => {
      return () => {
        arr.push('Unmount parent')
      }
    })
    return <Child />
  }

  function Child() {
    useEffect(() => {
      return () => {
        arr.push('Unmount child')
      }
    })
    return 'Child'
  }

  root.render(<Parent a={1} />)
  await sleep(10)
  if (root.getChildrenAsJSX() !== 'Child') {
    throw new Error('test1 failed')
  }

  root.render(null)
  await sleep(10)
  if (arr.join(',') !== 'Unmount parent,Unmount child') {
    throw new Error('test1 failed')
  }
}
```

Executing `test1` successfully indicates that our React Noop is working correctly.

Please kindly give me a star!!!