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.
yarn run build
npm run start:dev
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>
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.
In the create
function, we create a new element, which we will later use to render the React component into:
fn create(props: Self::Properties, mut link: ComponentLink<Self>) -> Self {
ReactCounter {
// ...
node: Node::from(
web_sys::window()
.unwrap()
.document()
.unwrap()
.create_element("div")
.unwrap(),
),
// ...
}
}
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),
// ...
}
}
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())
}
}
Based on yew-wasm-pack-template