Dux 0.0.3
dotnet add package Dux --version 0.0.3
NuGet\Install-Package Dux -Version 0.0.3
<PackageReference Include="Dux" Version="0.0.3"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Dux" Version="0.0.3" />
<PackageReference Include="Dux"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Dux --version 0.0.3
#r "nuget: Dux, 0.0.3"
#:package Dux@0.0.3
#addin nuget:?package=Dux&version=0.0.3
#tool nuget:?package=Dux&version=0.0.3
Dux
A source-generated Redux/Flux state management library for .NET.
Features
- Fully source-generated implementation
- Visibility of usage and calls into state store methods
- Step-through debugging of dispatched actions
- Minimal boilerplate
Installation
The Dux library makes use of several packages.
Add the main packages to your project:
dotnet add package Dux
dotnet add package Dux.Abstractions
If you are making use of Blazor, your project should also reference the Blazor package for tooling to make integration easier with components:
dotnet add package Dux.Blazor
Limitations
This library is new and so does not have the full suite of features and functionality that similar libraries have. Please be patient and understanding.
Some limitations include:
- At least one state object must be defined before the dependency injection extension method is generated.
- All store definitions must be defined within the 'root' project of your application, definitions within other projects referenced by the root will not be detected by the generator.
- Redux dev tool integrations are not presently supported.
- Middleware is not presently supported.
Usage
To maintain strong typing, the source generator operates against a set of interfaces as opposed to tagging with attributes, which is different to similar libraries.
Store definitions make use of these interfaces:
IDuxState
IDuxAction
IDuxReducer
Interfaces used to consume the store via dependency injection include:
IDuxStore<TYourState>
IDuxDispatcher
1. Define your state
State stores are defined by creating a class or record type with the IDuxState
marker interface.
For the best experience, state should be immutable. Only reducers acting on actions are allowed to mutate state, making use of record
types and immutable variants of collections
(like arrays and immutable/readonly lists) help to enforce this.
Note that state needs to have a parameterless constructor, which means it should also not make use of required
properties. Elements within the main state object, of course, do
not have this requirement and may be implemented as desired.
public record TodoState : IDuxState
{
public ImmutableList<Todo> Todos { get; init; } = ImmutableList<Todo>.Empty;
}
public record Todo
{
public required Guid Id { get; init; }
public requried string Value { get; init; }
}
2. Define actions
You may define your actions any way you like, but they need to make use of the IDuxAction
marker interface.
public record AddTodoAction(Todo Todo) : IDuxAction;
public record RemoveTodoAction(Guid TodoId) : IDuxAction;
3. Create reducers
Reducers are defined by implementing the IDuxReducer
interface, with any number of static or non-static methods. The individual reducers are detected based on the parameters in
each method: the first is the state to mutate (which will be passed in the call), the second is the action. The method must return the mutated state, which becomes the current state.
Any reducer may handle any dispatched action on behalf of any state.
public class TodoReducer : IDuxReducer
{
// Methods are discovered by parameter types
public TodoState HandleAddTodo(TodoState state, AddTodoAction action)
{
return state with { Todos = state.Todos.Add(action.Todo) };
}
public static TodoState HandleRemoveTodo(TodoState state, RemoveTodoAction action)
{
return state with {
Todos = state.Todos.Remove(
state.Todos.First(t => t.Id == action.TodoId)
)
};
}
}
4. Create effects (optional)
Effects may optionally be defined to perform async operations. Effects fire after reducers have completed.
Much like reducers, the individual effects within an IDuxEffect
are detected based on the parameters of each method: the first is the action, and the second is an optional
cancellation token. If you don't need or want a cancellation token, don't include it in the parameters. Effects may not be static.
The cancellation token comes from the point of dispatch - so if you dispatch an action in a Blazor page, and you navigate away, it will be canceled. Pass CancellationToken.None
explicitly when dispatching if you want the effect to continue executing in this case.
To dispatch other events as a result of an effect, use dependency injection to get a reference to the IDuxDispatcher
. To get the current state, the same applies - use dependency
injection to get a reference to the relevant IDuxStore<TState>
.
Any effect may handle any action.
public class TodoEffect : IDuxEffect
{
private readonly IMyTelemetryApi _telemetry;
public TodoEffect(IMyTelemetryApi telemetry)
{
_telemetry = telemetry;
}
public async ValueTask HandleAddTodo(AddTodoAction action, CancellationToken cancellationToken)
{
await _telemetry.TrackAsync($"Todo added: {action.Todo.Id}", cancellationToken);
}
}
5. Register services
services.AddDux(); // Registers everything automatically
Note that at least one IDuxState
must be defined within the project before the extension method is generated.
6. Use in your application
Resolve services like the dispatcher (IDuxDispatcher
) and any state (IDuxStore<TYourState>
) as you might expect, with dependency injection.
The Value
of any IDuxStore<T>
will contain the most up-to-date state object each time it is read.
To trigger your application to read the state again when it changes, you may hook into the StateChanged
event on any injected IDuxStore<T>
, or the StateChanged
event on the
IDuxDispatcher
for all events across all stores.
Additionally, there is an option to await all processing on a dispatched action by using the DispatchAsync
overload:
DispatchAsync(action, waitForCompletion: true, cancellationToken)
. If waitForCompletion
is false (including when using the other overloads without it), then the action is
processed in the background, the method returning as soon as it is in the processing queue.
public class TodoComponent
{
private readonly IDuxDispatcher _dispatcher;
private readonly IDuxStore<TodoState> _todoStore;
public TodoComponent(
IDuxDispatcher dispatcher,
IDuxStore<TodoState> todoStore)
{
_dispatcher = dispatcher;
_todoStore = todoStore;
}
public async ValueTask AddTodo(Todo todo, CancellationToken cancellationToken)
{
// Enqueue into the background
await _dispatcher.DispatchAsync(new AddTodoAction(todo), cancellationToken);
// Or: wait for all reducers and effects to complete before returning
await _dispatcher.DispatchAsync(new AddTodoAction(todo), waitForCompletion: true, cancellationToken);
}
}
6.1 Use in your Blazor application
When using the library in Blazor, the Dux.Blazor
package provides some base components to manage integration.
These base components are:
- DuxComponent - provides automatic render updates based on state updates
- CancellableDuxComponent - extends
DuxComponent
with a cancellation token for use when dispatching, in case you don't already have an implementation for cancellable components
The component bases also include a dispatcher property, since they also use it internally anyway.
Note that when using these components, if you override OnInitialized
, be sure to call base.OnInitialized()
so that these components may perform their own initialization.
@page "/"
@inherits CancellableDuxComponent // <- The base component
@inject IDuxStore<TodoState> TodoStore
<p>You have @TodoStore.Value.Todos.Count todos on your list</p>
<button @onclick="HandleAddTodo">Add random</button>
@code
{
private async Task HandleAddTodo()
{
var newTodo = new Todo { Id = Guid.NewGuid(), Value = "Something" };
await Dispatcher // <- From the inherited `DuxComponent`
.DispatchAsync(
new AddTodoAction(newTodo),
CancellationToken); // <- From the inherited `CancellableDuxComponent`
}
}
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- Dux.Abstractions (>= 0.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.