Implementing Redux With Rust
A step-by-step guide

Redux is a popular state management library for JavaScript applications. It allows developers to manage the state of their applications predictably and consistently, making it easier to develop and maintain complex applications.
In this blog post, we will explore how to implement Redux in Rust, a statically-typed systems programming language known for its performance and safety.
To implement Redux in Rust, we will need to create a struct that represents the state of our application and a trait that defines the actions that can be performed on the state. We will also need to create a reducer function that takes in the current state and an action and returns a new state based on the action.
Basic Implementation
Here is an example of a simple struct that represents the state of a to-do application:
struct TodoState {
todos: Vec<String>,
}
Next, we will define the actions that can be performed on the state using a trait:
trait TodoAction {
fn apply(&self, state: &mut TodoState);
}
We can then define the reducer function, which takes in the current state and an action, and returns a new state based on the action
fn todo_reducer(state: &TodoState, action: &dyn TodoAction) -> TodoState {
let mut new_state = state.clone();
action.apply(&mut new_state);
new_state
}
Now that we have the basic structure of our Redux implementation in place, we can define the specific actions that can be performed on our to-do state. For example, we might have an action for adding a new to-do:
struct AddTodoAction {
todo: String,
}
impl TodoAction for AddTodoAction {
fn apply(&self, state: &mut TodoState) {
state.todos.push(self.todo.clone());
}
}
We can also define an action for removing a to-do:
struct RemoveTodoAction {
index: usize,
}
impl TodoAction for RemoveTodoAction {
fn apply(&self, state: &mut TodoState) {
state.todos.remove(self.index);
}
}
With these actions in place, we can now use our reducer function to update the state of our to-do application based on user interactions. For example, if a user adds a new to-do, we can use the todo_reducer
function to update the state as follows:
let mut state = TodoState { todos: vec![] };
let action = AddTodoAction { todo: "Learn Rust".to_string() };
state = todo_reducer(&state, &action);
This is a very simple example of implementing Redux in Rust, but it should give you a good idea of the basic structure and how to create actions and a reducer function. There are many other ways to implement Redux in Rust, including using a macro to generate the reducer function and enums to represent actions.
Using a macro to generate the reducer function and enums to represent actions
First, we will define an enum that represents the different actions that can be performed on the state:
enum TodoAction {
AddTodo(String),
RemoveTodo(usize),
}
Next, we will define the macro that will generate the reducer function for us:
#[macro_export]
macro_rules! create_reducer {
($state_type:ty, $action_type:ty, $reducer_fn:expr) => {
fn reducer(state: &$state_type, action: $action_type) -> $state_type {
let mut new_state = state.clone();
$reducer_fn(&mut new_state, action);
new_state
}
}
}
Now we can use the create_reducer
macro to define our reducer function as follows:
create_reducer!(TodoState, TodoAction, |state: &mut TodoState, action| {
match action {
TodoAction::AddTodo(todo) => state.todos.push(todo),
TodoAction::RemoveTodo(index) => state.todos.remove(index),
}
});
This will generate a reducer function that takes in a TodoState
and a TodoAction
enum, and updates the state based on the action.
We can now use the reducer function to update the state of our to-do application as follows:
let mut state = TodoState { todos: vec![] };
let action = TodoAction::AddTodo("Learn Rust".to_string());
state = reducer(&state, action);
Now we can use Yew to build the UI for our to-do application
First, we will define a component that represents a single to-do item:
use yew::{html, Callback, Html};
struct TodoItem {
todo: String,
on_remove: Callback<()>,
}
impl Component for TodoItem {
type Message = ();
type Properties = Self;
fn create(props: Self::Properties, _: ComponentLink<Self>) -> Self {
Self {
todo: props.todo,
on_remove: props.on_remove,
}
}
fn update(&mut self, _: Self::Message) -> ShouldRender {
false
}
fn view(&self) -> Html {
html! {
<div>
<span>{ &self.todo }</span>
<button onclick=self.on_remove.clone()>{"Remove"}</button>
</div>
}
}
}
Next, we will define the main component for our application, which will contain a list of to-do items and a form for adding new to-dos:
use yew::{html, Callback, Component, ComponentLink, Html, ShouldRender};
use yew_functional::{use_state, use_reducer};
struct TodoApp {
link: ComponentLink<Self>,
state: TodoState,
dispatch: Callback<TodoAction>,
}
impl Component for TodoApp {
type Message = ();
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
let state = TodoState { todos: vec![] };
let (dispatch, _) = use_reducer(link, reducer, state);
Self { link, state, dispatch }
}
fn update(&mut self, _: Self::Message) -> ShouldRender {
false
}
fn view(&self) -> Html {
html! {
<div>
<h1>{"Todo List"}</h1>
<ul>
{ for self.state.todos.iter().enumerate().map(|(index, todo)| {
html! {
<TodoItem
todo=todo.clone()
on_remove=self.link.callback(move |_| {
self.dispatch.emit(TodoAction::RemoveTodo(index))
})
/>
}
}) }
</ul>
<form onsubmit=self.link.callback(|event| {
event.prevent_default();
let input = event.target().unwrap().try_into::<HtmlInputElement>().unwrap();
let todo = input.value();
input.set_value("");
self.dispatch.emit(TodoAction::AddTodo(todo))
})>
<input type="text" />
<button type="submit">{"Add Todo"}</button>
</form>
</div>
}
}
This component will render a list of to-do items and a form for adding new to-dos. The state of the application is managed using the use_reducer
hook from Yew, which allows us to use the reducer function we defined earlier to update the state based on user interactions.
To use this component in a Yew app, you can add it to the root component like this:
use yew::{html, App};
fn main() {
yew::initialize();
App::<TodoApp>::new().mount_to_body();
yew::run_loop();
}
This example helps you understand how to use Yew and Redux to build a web application in Rust. If you have any questions or suggestions, please don’t hesitate to ask!
Level Up Coding
Thanks for being a part of our community! Before you go:
- 👏 Clap for the story and follow the author 👉
- 📰 View more content in the Level Up Coding publication
- 🔔 Follow us: Twitter | LinkedIn | Newsletter
🚀👉 Join the Level Up talent collective and find an amazing job