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.
Adding Fluxor and initiating it in the project
Follow these steps to add Fluxor to your Blazor project:
- First, install the Fluxor.Blazor.Web NuGet package in the .Client project in your solution.
Install-Package Fluxor.Blazor.Web
- Then, register Fluxor by adding the following code in the Program.cs file, just before the line that starts with “await builder.Build()”:.
builder.Services.AddFluxor(options => options.ScanAssemblies(typeof(Program).Assembly));
- Define Fluxor using the following statement at the top of the Program.cs file.
using Fluxor;
- To initialize Fluxor with the store when the app starts, add the following line to the top of the App.razor file.
<Fluxor.Blazor.Web.StoreInitializer />
- Update the wwwroot/index.html file by adding the following code just above the </body> tag.
<script src="_content/Fluxor.Blazor.Web/scripts/index.js"></script>
- To enable the browser extension, install the Fluxor.Blazor.Web.ReduxDevTools NuGet package.
Install-Package Fluxor.Blazor.Web.ReduxDevTools
- Finally, in the Program.cs file, add the following line.
builder.Services.AddFluxor(options => { options.ScanAssemblies(typeof(Program).Assembly); options.UseReduxDevTools(); });
Creating the store
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:
- Actions
- Reducers
- States and Features
- Effects
Creating the state
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:
- The first method returns the name that Fluxor will use for the feature.
- The second method sets up the initial state of the OrderState.
Next, we need an action to dispatch while performing CRUD actions in the Blazor DataGrid.
Creating the action
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; } } }
Creating the reducers
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; } }
What are effects, and why are they needed?
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 } }
Integrating the Syncfusion Blazor DataGrid with Fluxor
Let’s integrate Fluxor with Syncfusion Blazor DataGrid to perform CRUD operations in it.
- To access Fluxor resources, simply add the following code in the Index.razor file.
@inherits FluxorComponent
- Add the OrderState and Fluxor Dispatcher, since we have to access and dispatch the actions.
@inject IDispatcher Dispatcher @inject Istate<OrdersState> OrdersState
- Next, we inject the IActionSubscriber. This will allow the Order class to subscribe to the action and be notified of changes in the state.
@inject IActionSubscriber ActionSubscriber
- Define the Syncfusion Blazor DataGrid in the same Index.razor file and directly bind the Orders collection from OrdersState to the grid using the DataSource property.
<SfGrid TValue="Order" ID="Grid" DataSource="@OrdersState.Value.Orders" Toolbar="Toolbaritems" AllowSorting="true" AllowFiltering="true" AllowPaging="true"> . . . . . . </SfGrid>
- In the following code example, in the OnInitialized and OnInitializedAsync lifecycle functions, we subscribe the actions Load, Update, Delete, and Add.
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 }
- Define the command column in DataGrid to perform the edit and delete operations using the Syncfusion Blazor Dialog component.
<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 we choose the Save button in the dialog component, then we have to dispatch the following action in it.
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 }
Performing CRUD operations
Let’s perform CRUD operations in the Blazor DataGrid using Fluxor!
Delete
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 } }
Add
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 }); }
Update
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
GitHub reference
Check out the complete example for this on GitHub demos.
Conclusion
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!
Related blogs
- To access Fluxor resources, simply add the following code in the Index.razor file.