Comparing React and Yew.rs by building a ToDo application

Radovan Stevanovic
Frontend Weekly
Published in
8 min readDec 19, 2022

--

If you're interested in front-end development, you've likely heard of React, a popular JavaScript library for building user interfaces. But have you heard of Yew.rs? It's a relatively new framework for building web applications in Rust, a programming language known for its performance and safety. We will be comparing React and Yew.rs by building a ToDo application!

In this post, we'll dive into the differences between React and Yew.rs by building a simple ToDo application in both frameworks. By the end, you'll have a better understanding of the trade-offs and benefits of each and be able to make an informed decision on which one is right for your project.

So let's get started!

Setup (React)

To set up a React application, you'll need to have Node.js and npm (the package manager for Node.js) installed on your machine.

  1. Please create a new directory for your project and navigate to it in your terminal.
  2. Run the following command to create a new React application:
npx create-react-app --template typescript my-app

3. Navigate into the new directory:

cd my-app
  1. Start the development server by running the following:
yarn start

This will compile and run the application and open a new browser window with the application running at http://localhost:3000/.

  1. You can start building your React application in the src directory. The entry point for the application is that you can create new components in the src/components directory.

Setup (Yew.rs)

To set up a Yew.rs application, you'll need to have Rust and cargo (the package manager and build tool for Rust) installed on your machine.

  1. Please create a new directory for your project and navigate to it in your terminal.
  2. Run the following command to create a new Yew.rs application:
cargo new my-app --lib

This will make a new Rust crate called "my-app" with a basic Yew.rs application set up.

3. Navigate into the new directory:

cd my-app

4. Add the Yew.rs dependency to your project by adding the following to the Cargo.toml file:

[dependencies]
yew = "0.17"
wasm-bindgen = "0.2"

5. Create an HTML file for your application, such as, and include the following in the <body>:

<div id="app"></div>
<script src="/pkg/my_app.js"></script>

6. In the Rust file src/lib.rs, replace the contents with the following to mount your Yew.rs application to the #app element in the HTML file:

use yew::{html, Callback, Component, ComponentLink, Html, Renderable, ShouldRender};

pub struct App {
link: ComponentLink<Self>,
}

pub enum Msg {
// No messages are needed for this example.
}

impl Component for App {
type Message = Msg;
type Properties = ();

fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Self { link }
}

fn update(&mut self, _msg: Self::Message) -> ShouldRender {
// No messages are handled in this example.
false
}

fn view(&self) -> Html {
html! {
<div>
<h1>{"Hello, World!"}</h1>
</div>
}
}
}

fn main() {
yew::start_app::<App>();
}

7. Run the following command to build and serve your Yew.rs application:

cargo web start

8. This will compile and run the application and open a new browser window with the application running at http://localhost:8000/.

9. You can start building your Yew.rs application in the src/lib.rs file. You can create new components by creating new Rust files and using the #[component] attribute provided by Yew.rs.

Simplest React Todo Application

  1. Create a new file src/ToDo.tsx with the following code for an essential ToDo component:
import React, { useState } from 'react';

type ToDoProps = {
text: string;
complete: boolean;
onClick: () => void;
}

const ToDo: React.FC<ToDoProps> = ({ text, complete, onClick }) => {
return (
<li
style={{
textDecoration: complete ? 'line-through' : 'none',
}}
onClick={onClick}
>
{text}
</li>
);
};

export default ToDo;

This defines a functional component for rendering a single ToDo item with a text prop for the ToDo text, a complete prop for its completion status and an onClick prop for a callback to toggle the completion status.

2. In src/App.tsx, import the ToDo component and use it to render a list of ToDo items. You can use the useState hook to manage the list of ToDo items and the map function to render a list of ToDo components:

import React, { useState } from 'react';
import ToDo from './ToDo';

const App: React.FC = () => {
const [toDos, setToDos] = useState([
{ text: 'Learn TypeScript', complete: true },
{ text: 'Build a ToDo app', complete: false },
]);

const toggleToDo = (index: number) => {
const newToDos = [...toDos];
newToDos[index] = { ...toDo, complete: !toDo.complete };
setToDos(newToDos);
};

return (
<div className="App">
<ul>
{toDos.map((toDo, index) => (
<ToDo
key={index}
text={toDo.text}
complete={toDo.complete}
onClick={() => toggleToDo(index)}
/>
))}
</ul>
</div>
);
};

export default App;

Simplest Yew.rs Todo Application

  1. Create a new Yew.rs application as described in the previous answer.
  2. In the src/lib.rs file, define a ToDo component using the #[component] attribute provided by Yew.rs:
use yew::{html, Component, ComponentLink, Html, Properties, ShouldRender};

#[derive(Properties, Clone, Debug)]
pub struct ToDoProps {
pub text: String,
pub complete: bool,
pub onclick: Callback<()>,
}

#[component]
pub struct ToDo {
props: ToDoProps,
}

impl Component for ToDo {
type Message = ();
type Properties = ToDoProps;

fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self { props }
}

fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false
}

fn view(&self) -> Html {
let style = if self.props.complete {
"line-through"
} else {
"none"
};
html! {
<li style=format!("text-decoration: {}", style) onclick=&self.props.onclick>{ &self.props.text }</li>
}
}
}

This defines a struct for the ToDo component with text, complete, and onclick properties. The view method returns an li element with the text property as its content and a style set based on the complete property. The onclick an attribute is set to the provided onclick callback.

3. In the main function, use the ToDo component to render a list of ToDo items. You can use a Vec to store the list of ToDo items and the iter function to render a list of ToDo components:

use yew::{html, Callback, Component, ComponentLink, Html, Properties, ShouldRender};

fn main() {
let app: App = App::new();
app.mount_to_body();
}

struct App {
todos: Vec<ToDoItem>,
}

impl App {
fn new() -> Self {
Self {
todos: vec![
ToDoItem {
text: "Learn Rust".to_string(),
complete: true,
},
ToDoItem {
text: "Build a ToDo app".to_string(),
complete: false,
},
],
}
}
}

impl Component for App {
type Message = ();
type Properties = ();

fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
Self::new()
}

fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false
}

fn view(&self) -> Html {
html! {
<div>
<ul>
{for self.todos.iter().enumerate().map(|(index, todo)| {
html! {
<ToDo
text=todo.text.clone()
complete=todo.complete
onclick=Callback::from(move |_| {
let mut new_todos = self.todos.clone();
new_todos[index].complete = !new_todos[index].complete;
self.todos = new_todos;
})
/>
}
})}
</ul>
</div>
}
}
}

This defines an `App` struct with a `todos` field to store the list of ToDo items. The `view` method iterates over the list of `todos` and renders a `ToDo` component for each one, passing the `text`, `complete`, and `onclick` properties. The `onclick` callback is created using the `Callback` type provided by Yew.rs and toggles the `complete` field of the appropriate ToDo item when called.

That's it! You now have a basic ToDo application built with Yew.rs.

Add/Edit/Remove ToDo

To add the ability to create, edit, and remove ToDo items in the React ToDo application, you can update the App component to handle these actions. Here's an example of how you could do this:

import React, { useState } from 'react';
import ToDo from './ToDo';

const App: React.FC = () => {
const [toDos, setToDos] = useState([
{ text: 'Learn TypeScript', complete: true },
{ text: 'Build a ToDo app', complete: false },
]);
const [newToDo, setNewToDo] = useState('');
const [editedToDo, setEditedToDo] = useState<{ index: number; text: string } | null>(null);

const addToDo = () => {
if (newToDo.trim().length > 0) {
setToDos([...toDos, { text: newToDo, complete: false }]);
setNewToDo('');
}
};

const editToDo = (index: number, text: string) => {
setEditedToDo({ index, text });
};

const saveEdit = () => {
if (editedToDo) {
const newToDos = [...toDos];
newToDos[editedToDo.index] = { text: editedToDo.text, complete: newToDos[editedToDo.index].complete };
setToDos(newToDos);
setEditedToDo(null);
}
};

const cancelEdit = () => {
setEditedToDo(null);
};

const removeToDo = (index: number) => {
const newToDos = [...toDos];
newToDos.splice(index, 1);
setToDos(newToDos);
};

const toggleToDo = (index: number) => {
const newToDos = [...toDos];
newToDos[index] = { ...toDo, complete: !toDo.complete };
setToDos(newToDos);
};

return (
<div className="App">
{editedToDo ? (
<>
<input value={editedToDo.text} onChange={(e) => setEditedToDo({ ...editedToDo, text: e.target.value })} />
<button onClick={saveEdit}>Save</button>
<button onClick={cancelEdit}>Cancel</button>
</>
) : (
<>
<input value={newToDo} onChange={(e) => setNewToDo(e.target.value)} />
<button onClick={addToDo}>Add</button>
</>
)}
<ul>
{toDos.map((toDo, index) => (
<ToDo
key={index}
text={toDo.text}
complete={toDo.complete}
onClick={() => toggleToDo(index)}
/>)}
</ul>
</div>
)

The same can be achieved in Yew.rs

use yew::{html, Callback, Component, ComponentLink, Html, Properties, ShouldRender};

fn main() {
let app: App = App::new();
app.mount_to_body();
}

struct App {
todos: Vec<ToDoItem>,
new_todo: String,
edited_todo: Option<(usize, String)>,
}

impl App {
fn new() -> Self {
Self {
todos: vec![
ToDoItem {
text: "Learn Rust".to_string(),
complete: true,
},
ToDoItem {
text: "Build a ToDo app".to_string(),
complete: false,
},
],
new_todo: "".to_string(),
edited_todo: None,
}
}

fn add_todo(&mut self) {
if !self.new_todo.is_empty() {
self.todos.push(ToDoItem {
text: self.new_todo.clone(),
complete: false,
});
self.new_todo.clear();
}
}

fn edit_todo(&mut self, index: usize, text: String) {
self.edited_todo = Some((index, text));
}

fn save_edit(&mut self) {
if let Some((index, text)) = self.edited_todo.take() {
self.todos[index].text = text;
}
}

fn remove_todo(&mut self, index: usize) {
self.todos.remove(index);
}
}

impl Component for App {
type Message = ();
type Properties = ();

fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Self::new()
}

fn update(&mut self, _msg: Self::Message) -> ShouldRender {
false
}

fn view(&self) -> Html {
let view_form = |todo_text: &str, button_text: &str, callback: Callback<()>| {
html! {
<form onsubmit=callback>
<input type="text" value=todo_text oninput=|e| {
callback.emit(());
e.prevent_default();
} />
<button type="submit">{button_text}</button>
</form>
}
};
html! {
<div>
{if let Some((_, ref text)) = self.edited_todo {
view_form(text, "Save", self.link.callback(|e: FocusEvent| {
e.prevent_default();
Self::save_edit();
}))
} else {
view_form(&self.new_todo, "Add", self.link.callback(|e: FocusEvent| {
e.prevent_default();
Self::add_todo();
}))
}}
<ul>
{for self.todos.iter().enumerate().map(|(index, todo)| {
html! {
<ToDo
text=todo.text.clone()
complete=todo.complete
onclick=self.link.callback(move |_| {
let mut new_todos = self.todos.clone();
new_todos[index].complete = !new_todos[index].complete;
self.todos = new_todos;
})
ondoubleclick=self.link.callback(move |_| {
Self::edit_todo(index, todo.text.clone());
})
oncontextmenu=self.link.callback(move |e: ContextMenuEvent| {
e.prevent_default();
Self::remove_todo(index);
})
/>
}
})}
</ul>
</div>
}
}
}

Final Words

There are several key differences between React and Yew.rs that you should consider when deciding which framework to use for your project.

One key difference is the language that each framework is written in. React is written in JavaScript, and Yew.rs is written in Rust. This means that if you are more familiar with Rust, you may find Yew.rs a better choice, while if you are more familiar with JavaScript, you may find React a better option.

Another key difference is the way that each framework handles state management. In React, the state is typically managed using the useState hook or a state management library like Redux. In Yew.rs, the state is managed using a Component struct that contains the current state of the application and methods to update the state.

Performance is also a consideration when comparing these two frameworks. React is known for its fast rendering performance, thanks to its virtual DOM implementation, which allows it to minimize the number of DOM updates required when the application's state changes. Yew.rs, on the other hand, Yew.rs uses a similar approach to React but with the added benefits of Rust's static typing and the ability to compile to WebAssembly. This can improve performance compared to React, particularly for larger and more complex applications.

Finally, it's worth considering the community and ecosystem surrounding each framework. React has a large and active community with many third-party libraries and tools available. Yew.rs is a newer framework with a smaller community, but it is increasing, and several valuable libraries and tools are already available.

Ultimately, deciding which framework to use will depend on your specific needs and preferences. React, and Yew.rs are powerful and popular frameworks that can be used to build high-quality web applications, so you should choose the one that best fits your project and your skill set.

--

--

Radovan Stevanovic
Frontend Weekly

Curiosity in functional programming, cybersecurity, and blockchain drives me to create innovative solutions and continually improve my skills