rust-redux
An attempt to implement Redux for Rust at the React Europe hackathon.
Why
If you like Redux elsewhere you might like it in Rust?
Current state of the project
This is a barebones Redux implementation, and examples/todo_list
uses Redux to handle the state of a todo app. Now, middleware is also supported, allowing for enhanced action handling like logging or async operations.
Running To-Do List Example
Usage
1. Creating a Store
let mut store = new
.with_middleware;
This is a standard store creation. Before creating a store, you will need to create a state model of what the store will be storing. We also need a root reducer function that will return an instance of our Redux state model that calls our other individual reducers. Let's start by building our state model.
2. Creating State Model
The State
type used in the to-do list example contains all parts of our rust-redux state.
Your state model does not have to be named "State" or have any specific methods of its own. However, it is useful to have a method similar to with_defaults
as we'll need to pass some default values into Store::new()
.
3. Creating a Root Reducer
You can think of the root reducer as the rust-redux substitute for combineReducers
in reduxjs. Our root reducer just needs to return our State
model where each property in our model is set to the return value of its individual reducer. The root reducer must be of type: fn(&T, U) -> T
4. Creating Actions
Actions can be whatever you want them to be. It is only up to your individual reducers to decide how to handle them. The way we decided to build actions was using Rust enums. They allow us to specify a type and a payload without adding extra syntax. Take a look at the Action
type created in the to-do example.
You will notice that we had to create a generic Action
wrapper around our other two action types. This isn't totally necessary as we could have stuffed all our actions inside a single type, but this is much cleaner. It is important to note that you can only pass a single action type into Store.dispatch()
. So, if you wish to have multiple action types, you must wrap them up into another type as we have done above, or you can throw all actions into a single type.
5. Creating Reducers
Individual reducers decide how you split up your store's state. Similar to reduxjs, reducers should accept some state and an action. Let's look at an example.
Another aspect of reduxjs that we want to adapt is avoiding direct mutation of the store's state. As you can see here, we create a clone of the state that is passed in and mutate the clone's properties instead of the state reference that was passed to the reducer. Mutating the passed-in state in this example isn't actually possible, as our todo_reducer
does not accept a mutable reference to Vec<Todo>
.
6. Middleware
Middleware allows you to modify or monitor actions before they reach the reducer. In this example, we've added a simple logger middleware that logs every action dispatched and the state after each action.
To add middleware to the store, use the with_middleware
method when creating the store:
let mut store = new
.with_middleware;
This allows you to see all actions being dispatched and the resulting state, making debugging easier.
7. Putting it All Together
Now that we have all of our pieces in place, let's subscribe, dispatch, and get our store's state!
Dispatching Actions
use *;
Subscribing
Subscribing allows us to have functions that listen to the store's state directly. Whenever an action is dispatched to the store, these functions are called again with the updated state.
Getting State
The store's get_state
method returns an immutable reference to the current state in the rust-redux store.