Home

Awesome

About

This template shows how to create a web app using a React component inside a Yew component.

Similar to yew, this uses web_sys by default, but there is also a stdweb variant.

🚴 Usage

🛠️ Build with npm run build

yarn run build

🔬 Serve locally with yarn npm start:dev

npm run start:dev

🔎 Explanation

Including dependencies

In the index.html, we include react and react-dom as UMD packages (See React docs).

Additionally we include material-ui so that we have some components available the we can use in the example.

  <script crossorigin src="./react.development.js"></script>
  <script crossorigin src="./react-dom.development.js"></script>
  <script crossorigin src="./material-ui.development.js"></script>

Yew component that uses a React component

Inside src/react.rs (web_sys variant) src/react_stdweb.rs (stdweb variant) you can find the Yew component ReactCounter that internally uses a React component to display a button with an incrementing counter.

Constructor (fn create)

In the create function, we create a new element, which we will later use to render the React component into:

web_sys variant
fn create(props: Self::Properties, mut link: ComponentLink<Self>) -> Self {
    ReactCounter {
        // ...
        node: Node::from(
            web_sys::window()
                .unwrap()
                .document()
                .unwrap()
                .create_element("div")
                .unwrap(),
        ),
        // ...
    }
}
stdweb variant
fn create(props: Self::Properties, mut link: ComponentLink<Self>) -> Self {
    ReactCounter {
        // ...
        node: stdweb::web::document()
            .create_element("div")
            .unwrap()
            .try_into()
            .unwrap(),
        // ...
    }
}

We also create a Callback wrapper, which we need to create a Message for our Component from a JS callback:

fn create(props: Self::Properties, mut link: ComponentLink<Self>) -> Self {
    ReactCounter {
        // ...
        react_counter_cb: Self::link_react_counter_cb(&mut link),
        // ...
    }
}

Rendering the component (fn view) (stdweb variant)

First we create a closure, that triggers our Callback wrapper, which we can use in the js! macro:

impl Renderable<ReactCounter> for ReactCounter {
    fn view(&self) -> Html<Self> {
        let orig_callback = self.react_counter_cb.clone();
        let callback = move || orig_callback.emit(());
        // ...
    }
}

We prepare a label with the counter that we will then pass to the React component as a prop:

impl Renderable<ReactCounter> for ReactCounter {
    fn view(&self) -> Html<Self> {
        // ...
        let label = format!(
            "Native count: {} - React count: {}",
            self.props.native_counter, self.react_counter
        );
        // ...
    }
}

Now we come to the rendering of the React component.

Inside the js! macro we first create a React element instance of the MaterialUI.Chip component (MaterialUI.Button has more complicated props requirements). As a second argument we pass in the props as an object that contains both our label and the callback which serves as a onClick handler.

We then use ReactDOM.render to render the React element into the Node we created earlier.

impl Renderable<ReactCounter> for ReactCounter {
    fn view(&self) -> Html<Self> {
        // ...
        js! {
            let element = React.createElement(MaterialUI.Chip,
                {
                  label: @{label},
                  onClick: () => @{callback}(),
                }
              );
            ReactDOM.render(element, @{self.node.clone()});
        }
        // ...
    }
}

Lastly we return the node we are rendering into as a virtual DOM reference from the view function, so the Yew renderer knows where to attach it to in the Yew component tree.

impl Renderable<ReactCounter> for ReactCounter {
    fn view(&self) -> Html<Self> {
        // ...
        yew::virtual_dom::VNode::VRef(self.node.clone())
    }
}

Here is a complete view of the view function:

impl Renderable<ReactCounter> for ReactCounter {
    fn view(&self) -> Html<Self> {
        // Wrap callback in a closure that we can use in the js! macro
        let orig_callback = self.react_counter_cb.clone();
        let callback = move || orig_callback.emit(());

        let label = format!(
            "Native count: {} - React count: {}",
            self.props.native_counter, self.react_counter
        );
        js! {
            let element = React.createElement(MaterialUI.Chip,
                {
                  label: @{label},
                  onClick: () => @{callback}(),
                }
              );
            ReactDOM.render(element, @{self.node.clone()});
        }

        yew::virtual_dom::VNode::VRef(self.node.clone())
    }
}

🙏 Acknowledgements

Based on yew-wasm-pack-template