Fluxor is a useful tool for managing application states in a centralized manner, allowing for easier traceability of changes. It is a zero-boilerplate state management library for .NET that operates on the Flux and Redux principles. The goal of Fluxor is to simplify front-end development by providing a single-state store approach for multiple UIs, without the hassle of excessive boilerplate code. The Fluxor concepts are well-explained by Peter Morris and the Fluxor team in the following links:
In this blog, we’ll see how to integrate Fluxor with the Syncfusion Blazor DataGrid and perform CRUD actions using state management principles.
Note: Before proceeding, refer to the Blazor DataGrid documentation for getting started with the control.
Follow these steps to add Fluxor to your Blazor project:
Install-Package Fluxor.Blazor.Web
builder.Services.AddFluxor(options => options.ScanAssemblies(typeof(Program).Assembly));
using Fluxor;
<Fluxor.Blazor.Web.StoreInitializer />
<script src="_content/Fluxor.Blazor.Web/scripts/index.js"></script>
Install-Package Fluxor.Blazor.Web.ReduxDevTools
builder.Services.AddFluxor(options => { options.ScanAssemblies(typeof(Program).Assembly); options.UseReduxDevTools(); });
Before integrating Fluxor with the Syncfusion Blazor DataGrid component, it’s necessary to create a Fluxor store. This is because CRUD operations are performed on state data, which is managed in a centralized manner. The store holds the entire app’s state, and all updates are performed through actions and reducers. The Fluxor store consists of the following folders:
We next need to create a state. The state defines the data structure and serves as the centralized store for the entire app. In this case, we have created the OrderState.
Refer to the following code example.
public record OrdersState: OrderLoadableState { public List<Order> Orders { get; init; } } public class OrdersFeatureState : Feature<OrdersState> { public override string GetName() { return nameof(OrdersState); } protected override OrdersState GetInitialState() { return new OrdersState { ... }; } }
The previous code example shows the addition of a Feature class that exposes the OrderState to Fluxor. This allows all updates to the state to be performed through actions and reducers. These updates are crucial in maintaining the integrity of the centralized state and ensuring that it accurately reflects the entire app’s state.
The Fluxor Feature<T> class requires the implementation of two abstract methods:
Next, we need an action to dispatch while performing CRUD actions in the Blazor DataGrid.
Actions play an important role in Fluxor architecture. They are objects that describe changes to the state and are dispatched from components to be processed by reducers.
To perform CRUD operations with Fluxor, specific actions must be created for each operation. For instance, if you want to implement CRUD state management for a list of items, you should create actions for adding, updating, deleting, and retrieving the list of items.
Refer to the following code example to implement these actions.
namespace FluxorSyncfusionGrid.Client.OrderStore.Actions { public record SetOrderAction { public Order Order { get; set; } } public record UpdateOrderAction { public Order Order { get; set; } } public record ResetOrderAction { } public record AsyncLoadOrderAction { public int Id { get; set; } } public record AsyncUpdateOrderAction { public Order Order { get; set; } } public record AsyncAddOrderAction { public Order Order { get; set; } } public record AsyncDeleteOrderAction { public Order Order { get; set; } } }
As discussed in the Action section, we require a reducer method to handle the actions. Reducers play a crucial role in the Fluxor architecture by updating the state in response to dispatched actions. They are defined as functions that receive the current state and an action and then return a new state that reflects the changes described by the action.
For example, in the case of a CRUD state management for a list of items, the reducer would handle actions such as adding, updating, deleting, and retrieving the list of items, updating the state accordingly.
Refer to the following code example.
public static class OrderReducer { [ReducerMethod] public static OrderState OnSetOrder(OrderState state, SetOrderAction action) { return state with { . . . }; } [ReducerMethod] public static OrderState OnUpdateOrder(OrderState state, UpdateOrderAction action) { return state with { . . . }; } . . . }
We can see that the UpdateOrder reducer method receives a currentState as a parameter and returns a new state with the updated values.
We now have to create the OrderService to fetch the data from the API and corresponding CRUD action methods. Refer to the following code example.
public class OrderService { public async Task<List<Order>> GetOrders(int limit = 10) { List<Order> items; items = await this.http.GetFromJsonAsync<List<Order>>($"{this.Url}posts?_limit={limit}"); return items; } public async Task<Order> AddOrder(Order Order) { HttpResponseMessage response = await this.http.PostAsJsonAsync($"{this.Url}posts", Order); Order savedOrder = await response.Content.ReadFromJsonAsync<Order>(); return savedOrder; } public async Task<Order> UpdateOrder(Order Order) { HttpResponseMessage response = await this.http.PutAsJsonAsync($"{this.Url}posts/{Order.Id}", Order); Order savedOrder = await response.Content.ReadFromJsonAsync<Order>(); return savedOrder; } public async Task<bool> DeleteOrder(Order Order) { await this.http.DeleteAsync($"{this.Url}posts/{Order.Id}"); return true; } }
Effects in Fluxor are utilized to separate the state update logic from the side effect logic. This separation makes the code easier to understand and maintain, as it clearly defines which portions of the code are responsible for updating the state and which are responsible for the side effects.
Effects are employed in Fluxor when a dispatched action requires access to resources outside the store. For instance, you could use an effect to retrieve data from a remote API in response to an action request. The effect would make the API call, and when the data is returned, it will dispatch another action to update the state with the obtained data. This separation of the logic of fetching data and updating the state enhances the code’s readability and maintainability.
In this example, we use the Effects method to fetch data from a remote service.
public class OrdersEffects { private readonly IState<OrdersState> State; private readonly OrderService OrderService; public OrdersEffects(IState<OrdersState> state, OrderService OrderService) { State = state; OrderService = OrderService; } [EffectMethod] public async Task AsyncLoadOrdersEffect(AsyncLoadOrdersAction action, IDispatcher dispatcher) { List<Order> Orders = await this.OrderService.GetOrders(); //fetch the data from remote API dispatcher.Dispatch(new SetOrdersAction { Orders = Orders }); //dispatch the action to set the Order data for maintaining the state } }
Let’s integrate Fluxor with Syncfusion Blazor DataGrid to perform CRUD operations in it.
@inherits FluxorComponent
@inject IDispatcher Dispatcher @inject Istate<OrdersState> OrdersState
@inject IActionSubscriber ActionSubscriber
<SfGrid TValue="Order" ID="Grid" DataSource="@OrdersState.Value.Orders" Toolbar="Toolbaritems" AllowSorting="true" AllowFiltering="true" AllowPaging="true"> . . . . . . </SfGrid>
protected override void OnInitialized() { base.OnInitialized(); ActionSubscriber.SubscribeToAction<AsyncUpdateOrderAction>(this, (Action) => { ChangeIdToChange(); }); ActionSubscriber.SubscribeToAction<AsyncLoadOrderAction>(this, (Action) => { ChangeIdToChange(0); }); ActionSubscriber.SubscribeToAction<ResetOrderAction>(this, (Action) => { ChangeIdToChange(0); }); }
protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); if (!OrdersState.Value.Orders.Any()) { await InvokeAsync(() => { Dispatcher.Dispatch(new AsyncLoadOrdersAction()); }); } }
So initially, the data will be fetched from the Effects method and then stored in the State since the AsyncLoadOrderAction was subscribed. Refer to the following code example.
[EffectMethod] public async Task AsyncLoadOrdersEffect(AsyncLoadOrdersAction action, IDispatcher dispatcher) { List<Order> orders = await this.OrderService.GetOrders(); // Fetch the data dispatcher.Dispatch(new SetOrdersAction { Orders = orders }); // Dispatch the next action }
<SfDialog @ref="Dialog" Height="400px" Width="600px" @bind-Visible="@IsVisible" ShowCloseIcon="true" IsModal="true" > <DialogTemplates> <Header>Order Information</Header> <Content> <RenderOrderEditForm IsAdd="@IsAdd" EditFormButtonPressed="HandleFormButtonClick" Data="@RowData"/> </Content> </DialogTemplates> <DialogPositionData X="@Xvalue" Y="@Yvalue"></DialogPositionData> </SfDialog>
if (Order.Id != 0) { Dispatcher.Dispatch(new AsyncUpdateOrderAction { Order = Order }); }The dispatch action will trigger the corresponding AsyncUpdateOrderEffect, where an API call is made to add the data to the state. Refer to the following code example for reference.
[EffectMethod] public async Task AsyncUpdateOrderEffect(AsyncUpdateOrderAction action, IDispatcher dispatcher) { Order order = await this.OrderService.UpdateOrder(action.Order); // Call the OrderService method to update the data in the remote API dispatcher.Dispatch(new UpdateOrderAction { Order = order }); await Task.Delay(1); dispatcher.Dispatch(new UpdateSingleOrderOrdersAction { Order = order }); // Update internally by calling the corresponding action state }
Let’s perform CRUD operations in the Blazor DataGrid using Fluxor!
If we delete an Order using the CommandColumn button, it will call the OnDelete method. In this method, we will dispatch the AsyncDeleteOrderAction.
if (args.CommandColumn.Title.Equals("Delete")) { OnDelete(Order); }
public void OnDelete(Order item) { Dispatcher.Dispatch(new AsyncDeleteOrderAction { Order = item }); }
The dispatch action will trigger the corresponding AsyncDeleteOrderEffect, where an API call is made to delete the data in the state.
[EffectMethod] public async Task AsyncDeleteOrderEffect(AsyncDeleteOrderAction action, IDispatcher dispatcher) { bool isDeleted = await this.OrderService.DeleteOrder(action.Order); // Call the API method to delete the Order item if (isDeleted) { dispatcher.Dispatch(new ResetOrderAction()); await Task.Delay(1); dispatcher.Dispatch(new DeleteSingleOrderOrdersAction { Order = action.Order }); // Update internally by calling the corresponding action state } }
Let’s add a new record to the DataGrid using the custom toolbar item. Refer to the following code example.
<SfGrid TValue="Order" ID="Grid" DataSource="@OrdersState.Value.Orders" Toolbar="Toolbaritems" AllowSorting="true" AllowFiltering="true" AllowPaging="true"> . . . . . . </SfGrid> @code{ protected override void OnInitialized() { . . . . . . Toolbaritems.Add(new ItemModel() { Text = "New Order", TooltipText = "Add new"}); } }
The Dialog component will be displayed when we press the New Order button in the DataGrid toolbar. After entering the inputs, click Save.
Dispatcher.Dispatch(new AsyncAddOrderAction { Order = Order });
The dispatch action will trigger the corresponding AsyncAddOrderEffect, where an API call is made to add the data to the state.
[EffectMethod] public async Task AsyncAddOrderEffect(AsyncAddOrderAction action, IDispatcher dispatcher) { Order order = await this.OrderService.AddOrder(action.Order); order.Id = OrdersState.Value.Orders.Max(p => p.Id) + 1; dispatcher.Dispatch(new UpdateOrderAction { Order = order }); await Task.Delay(1); dispatcher.Dispatch(new UpdateSingleOrderOrdersAction { Order = order }); }
The DataGrid UI will be automatically updated when CRUD actions are completed and stored in the state without requiring manual refresh or any other actions to reflect the changes in the grid.
After executing the previous code examples, the output will look like the following image.
Performing CRUD actions in Blazor DataGrid with Fluxor
Check out the complete example for this on GitHub demos.
Thanks for reading! In this blog, we’ve learned how to integrate the Syncfusion Blazor DataGrid with Fluxor and perform CRUD operations in it. Feel free to try out these steps and share your feedback in the comments section. For more details, refer to the online demos.
Syncfusion DataGrid is also available in the ASP.NET (Core, MVC), Blazor, Angular, JavaScript, React, Vue, Xamarin, Flutter, WinForms, WPF, .NET MAUI and WinUI platforms. Use this component wherever you need to build world-class apps!
The latest version of Essential Studio® is available for existing customers from the License and Downloads page. If you are not yet a Syncfusion customer, you can try our 30-day free trial to check out all the available features.
For questions, you can contact us through our support forums, support portal, or feedback portal. We are always happy to assist you!