In this lesson, you will learn how to encapsulate multi-step onboarding processes into a robust state machine, ensuring your wizard components remain decoupled from the specific logic of each step.
The State Machine Approach
A wizard is essentially a finite state machine where each step represents a state, and navigation actions (Next, Previous, Submit) act as transitions. To make this reusable, we avoid hardcoding the steps inside the wizard component itself. Instead, we define a common interface for steps and use a centralized state controller to manage the flow.
Defining the Step Interface
First, define an IWizardStep interface to allow the wizard to treat every view uniformly:
public interface IWizardStep
{
string Title { get; }
bool IsValid();
Task OnBeforeNextAsync();
}
The Wizard Controller
The core of a reusable wizard is a WizardState class that tracks the current index and the collection of registered steps. By injecting this state into your wizard component, you can separate the UI rendering logic from the navigation logic.
public class WizardState
{
private List<IWizardStep> _steps = new();
public int CurrentIndex { get; private set; }
public IWizardStep CurrentStep => _steps[CurrentIndex];
public void RegisterStep(IWizardStep step) => _steps.Add(step);
public void MoveNext()
{
if (CurrentStep.IsValid()) CurrentIndex++;
}
}
Decoupling the UI
In your Blazor component, you simply render the CurrentStep. Because the WizardState handles the registration and the index tracking, your Razor markup becomes incredibly clean:
<div class="wizard-container">
<h2>@State.CurrentStep.Title</h2>
<DynamicComponent Type="GetStepType(State.CurrentStep)" />
<button @onclick="State.MovePrevious">Back</button>
<button @onclick="State.MoveNext">Next</button>
</div>
By leveraging this architecture, you can drop the same Wizard component into any part of your application—whether it's for user onboarding, complex form submissions, or configuration flows—simply by providing a different list of IWizardStep implementations. This keeps your business logic modular and your components highly testable.
Key Takeaway: By modeling your wizard as a finite state machine using an interface-driven approach, you decouple navigation logic from step content, allowing for truly reusable and maintainable multi-step flows.