TL;DR: All the code in this post can be found in this repo (the different iterations contained in branches) https://github.com/stavroskasidis/BlazorSimpleState.BlogPostSample
When you start creating complex applications using Blazor, you will soon realize that you need a way to manage your application’s state. If you don’t, your code will start to fill with events being passed down to child components, so that parent components can stay up-to-date.
Let’s use an example to see how that looks like.
We will create a page that shows a “counter”, having two buttons: an “increment counter” button that increases the counter and a “reset counter” button that sets the counter back to zero.
Note: The example is quite simple, all of it could be a single component but for demo purposes we will break it up into multiple small components.
Iteration 1 - Using events (no “state management”)
Let’s how we could approach this using events. We will create 4 components:
- Counter.razor - This will be our “page” component, in other words, the component that represents the whole page.
- CounterDisplay.razor - This will be the component that shows the current count and hosts the two buttons.
- CounterIncrementButton.razor - This is the button that increments the counter.
- CounterResetButton.razor - This is the button that resets the counter.
Here’s what they look like:
As you can see, we are passing down events to the component tree so that the “root” component (Counter.razor) stays updated. This is required because Blazor components will re-render only if:
- Their parameters have changed.
- After an EventCallback is invoked.
- You invoke the
StateHasChanged()method inside the component.
Well… this kinda sucks !!
We are passing down events everywhere, plus the counter’s state logic is included in the component’s code itself. This is where “state management” as a concept comes to help us.
So what is “state management” ?
No matter what pattern/library you decide to use, it all boils down to a couple simple principles:
- You need a State Container: a read-only object that will hold the state of your page.
- You also need a way to update the state throught a controlled API.
These libraries are supposed to be super optimal in re-rendering, that is why they usually bring quite a bit of boilerplate code in my opinion.
However, if you want something simple and you want to avoid 3rd party dependencies, you can create your own state management following the principles above.
Iteration 2 - Using a “State” object
So, we need a read-only object that will hold the state of the counter. The object will be passed down to all child components as a CascadingParameter and it will contain public methods to allow state to be changed.
It will also contain an OnCountChanged event so that the “page” component (Counter.razor) will subscribe to, listening for changes to re-render itself.
This state object will be created in the “page” component (Counter.razor) and the child components will receive state info through parameters.
⚠️ It is important that the child components use parameters to read any data they need and NOT read them directly of the state, otherwise the UI may not update correctly in some cases.
Here is how the components will look like with the
This is an improvement over the the first iteration, however this solution is still mediocre in my opinion. The problem is that you have to pass down the state object and it may be “misused” by the child components if they read data directly from the state.
To improve on this concept, we need another class that will “manage” the state without exposing the state itself down the tree, while keeping it read-only.
This is where C# ‘s nested classes come into play, because a nested class can access the private members of the parent class.
Iteration 3 - Using a “StateManager” nested class
So, we will create a “StateManager” class, nested in the “State” class, that will contain all the logic for altering the state. This new class will then be passed down the tree for the child components to consume.
In my opinion, this is a much cleaner approach. We’ve seperated the state from the logic that updates it and as a bonus the child components cannot misuse the state object.
I like this approach and this is what I am currently using in my projects. Maybe this is not as optimal as a dedicated state management library theoretically is, but this way you are avoiding boilerplate code and extra dependencies.
What do you think? Would you do something like this or use a library? Feel free to comment your own ideas and suggestions.
Thank you for reading.