diff --git a/Images/flux-pattern.jpg b/Images/flux-pattern.jpg
new file mode 100644
index 00000000..ae8936d3
Binary files /dev/null and b/Images/flux-pattern.jpg differ
diff --git a/README.md b/README.md
index 5b849382..1158704e 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,20 @@ You can download the latest release / pre-release NuGet packages from the offici
* [Fluxor.Blazor.Web](https://www.nuget.org/packages/Fluxor.Blazor.Web) [![NuGet version (Fluxor)](https://img.shields.io/nuget/v/Fluxor.Blazor.Web.svg?style=flat-square)](https://www.nuget.org/packages/Fluxor.Blazor.Web/)
* [Fluxor.Blazor.Web.ReduxDevTools](https://www.nuget.org/packages/Fluxor.Blazor.Web.ReduxDevTools) [![NuGet version (Fluxor.Blazor.Web.ReduxDevTools)](https://img.shields.io/nuget/v/Fluxor.Blazor.Web.ReduxDevTools.svg?style=flat-square)](https://www.nuget.org/packages/Fluxor.Blazor.Web.ReduxDevTools/)
+## Flux pattern
+
+Often confused with Redux. Redux is the name of a library, Flux is the name of the pattern that Redux and
+Fluxor implement.
+
+![](./Images/flux-pattern.jpg)
+
+* State should always be read-only.
+* To alter state our app should dispatch an action.
+* The store runs the action through every registered reducer.
+* Every reducer that processes the dispatched action type will create new state
+from the existing state, along with any changes intended by the dispatched action.
+* The UI then uses the new state to render its display.
+
# Licence
[MIT](https://opensource.org/licenses/MIT)
diff --git a/Samples/Blazor/02EffectsSample/EffectsSample/Client/Pages/FetchData.razor b/Samples/Blazor/02EffectsSample/EffectsSample/Client/Pages/FetchData.razor
index fc9fdc93..f82c1032 100644
--- a/Samples/Blazor/02EffectsSample/EffectsSample/Client/Pages/FetchData.razor
+++ b/Samples/Blazor/02EffectsSample/EffectsSample/Client/Pages/FetchData.razor
@@ -21,17 +21,14 @@ else
- @if (WeatherState.Value.Forecasts != null)
+ @foreach (var forecast in WeatherState.Value.Forecasts)
{
- foreach (var forecast in WeatherState.Value.Forecasts)
- {
-
- @forecast.Date.ToShortDateString() |
- @forecast.TemperatureC |
- @forecast.TemperatureF |
- @forecast.Summary |
-
- }
+
+ @forecast.Date.ToShortDateString() |
+ @forecast.TemperatureC |
+ @forecast.TemperatureF |
+ @forecast.Summary |
+
}
diff --git a/Samples/Blazor/02EffectsSample/EffectsSample/Client/Store/WeatherUseCase/WeatherState.cs b/Samples/Blazor/02EffectsSample/EffectsSample/Client/Store/WeatherUseCase/WeatherState.cs
index 8154cfef..4be892f5 100644
--- a/Samples/Blazor/02EffectsSample/EffectsSample/Client/Store/WeatherUseCase/WeatherState.cs
+++ b/Samples/Blazor/02EffectsSample/EffectsSample/Client/Store/WeatherUseCase/WeatherState.cs
@@ -1,4 +1,5 @@
using FluxorBlazorWeb.EffectsSample.Shared;
+using System;
using System.Collections.Generic;
namespace FluxorBlazorWeb.EffectsSample.Client.Store.WeatherUseCase
@@ -11,7 +12,7 @@ public class WeatherState
public WeatherState(bool isLoading, IEnumerable forecasts)
{
IsLoading = isLoading;
- Forecasts = forecasts;
+ Forecasts = forecasts ?? Array.Empty();
}
}
}
diff --git a/Samples/Blazor/02EffectsSample/README.md b/Samples/Blazor/02EffectsSample/README.md
new file mode 100644
index 00000000..b57530fa
--- /dev/null
+++ b/Samples/Blazor/02EffectsSample/README.md
@@ -0,0 +1,227 @@
+# Fluxor - Blazor Web Samples
+
+## Effects
+
+Flux state is supposed to be immutable, and that state replaced only by
+[pure functions](https://en.wikipedia.org/wiki/Pure_function), which should only take input from their
+parameters.
+
+With this in mind, we need something that will enable us to access other sources of data such as
+web services, and then reduce the results into our state.
+
+### Goal
+This tutorial will recreate the `Fetch data` page in a standard Blazor app.
+
+### Steps
+
+- Under the `Store` folder, create a new folder named `WeatherUseCase`.
+- Create a new state class to hold the state for this use case.
+
+```c#
+public class WeatherState
+{
+ public bool IsLoading { get; }
+ public IEnumerable Forecasts { get; }
+
+ public WeatherState(bool isLoading, IEnumerable forecasts)
+ {
+ IsLoading = isLoading;
+ Forecasts = forecasts ?? Array.Empty();
+ }
+}
+```
+
+This state holds a property indicating whether or not the data is currently being retrieved from
+the server, and an enumerable holding zero to many `WeatherForecast` objects.
+
+*Note: Again, the state is immutable*
+
+- Create a new class named `Feature`. This class describes the state to the store.
+
+```c#
+public class Feature : Feature
+{
+ public override string GetName() => "Weather";
+ protected override WeatherState GetInitialState() =>
+ new WeatherState(
+ isLoading: false,
+ forecasts: null);
+}
+```
+
+#### Displaying state in the component
+
+- Find the `Pages` folder and add a new file named `FetchData.razor.cs`
+- Mark the class `partial`.
+- Add the following `using` declarations
+
+```c#
+using Fluxor;
+using Microsoft.AspNetCore.Components;
+using YourAppName.Store.WeatherUseCase;
+```
+
+- Next we need to inject the `WeatherState` into our component
+
+```c#
+public partial class FetchData
+{
+ [Inject]
+ private IState WeatherState { get; set; }
+}
+```
+
+- Edit `FetchData.razor` and make the page descend from `FluxorComponent`.
+
+```
+@inherits Fluxor.Blazor.Web.Components.FluxorComponent
+```
+
+- Change the mark-up so it uses our `IsLoading` state to determine if data is being
+retrieved from the server or not.
+
+Change
+
+`@if (forecasts == null)`
+
+to
+
+`@if (WeatherState.Value.IsLoading)`
+
+- Change the mark-up so it uses our `Forecasts` state.
+
+Change
+
+`@foreach (var forecast in forecasts)`
+
+to
+
+`@foreach (var forecast in WeatherState.Value.Forecasts)`
+
+- Remove `@inject WeatherForecastService ForecastService`
+
+#### Using an Action and a Reducer to alter state
+
+- Create an empty class `FetchDataAction`.
+- Create a static `Reducers` class, which will set `IsLoading` to true when our
+`FetchDataAction` action is dispatched.
+
+```c#
+public static class Reducers
+{
+ [ReducerMethod]
+ public static WeatherState ReduceFetchDataAction(WeatherState state, FetchDataAction action) =>
+ new WeatherState(
+ isLoading: true,
+ forecasts: null);
+}
+```
+
+- In `Fetchdata.razor.cs` inject `IDispatcher` and dispatch our action from the `OnInitialized`
+lifecycle method. The code-behind class should now look like this
+
+```c#
+public partial class FetchData
+{
+ [Inject]
+ private IState WeatherState { get; set; }
+
+ [Inject]
+ private IDispatcher Dispatcher { get; set; }
+
+ protected override void OnInitialized()
+ {
+ base.OnInitialized();
+ Dispatcher.Dispatch(new FetchDataAction());
+ }
+}
+```
+
+#### Requesting data from the server via an `Effect`
+
+Effect handlers do not (and cannot) affect state directly. They are triggered when the action they are
+waiting for is dispatched through the store, and as a response they can dispatch new actions.
+
+Effect handlers can be written in one of two ways.
+
+1. By descending from the `Effect` class. The name of the class is unimportant.
+
+```c#
+public class FetchDataActionEffect : Effect
+{
+ private readonly HttpClient Http;
+
+ public FetchDataActionEffect(HttpClient http)
+ {
+ Http = http;
+ }
+
+ protected override async Task HandleAsync(FetchDataAction action, IDispatcher dispatcher)
+ {
+ var forecasts = await Http.GetJsonAsync("WeatherForecast");
+ dispatcher.Dispatch(new FetchDataResultAction(forecasts));
+ }
+}
+```
+
+2. By decorating instance or static methods with `[EffectMethod]`. The name of the class and the
+method are unimportant.
+
+```c#
+public class Effects
+{
+ private readonly HttpClient Http;
+
+ public Effects(HttpClient http)
+ {
+ Http = http;
+ }
+
+ [EffectMethod]
+ public async Task HandleFetchDataAction(FetchDataAction action, IDispatcher dispatcher)
+ {
+ var forecasts = await Http.GetJsonAsync("WeatherForecast");
+ dispatcher.Dispatch(new FetchDataResultAction(forecasts));
+ }
+}
+```
+
+Both techniques work equally well, which you choose is an organisational choice. However, if your effect
+requires lots of (or unique) dependencies then you should consider having the handling method in its
+own class for simplicity (still either approach #1 or #2 may be used).
+
+#### Reducing the `Effect` result into state
+
+- Create a new class `FetchDataResultAction`, which will hold the results of the call to the server
+so they can be "reduced" into our application state.
+
+```c#
+public class FetchDataResultAction
+{
+ public IEnumerable Forecasts { get; }
+
+ public FetchDataResultAction(IEnumerable forecasts)
+ {
+ Forecasts = forecasts;
+ }
+}
+```
+
+This is the action that is dispatched by our `Effect` earlier, after it has retrieved the data from
+the server via an HTTP request.
+
+- Edit the `Reducers.cs` class and add a new `[ReducerMethod]` to reduce the contents of this result
+action into state.
+
+```c#
+[ReducerMethod]
+public static WeatherState ReduceFetchDataResultAction(WeatherState state, FetchDataResultAction action) =>
+ new WeatherState(
+ isLoading: false,
+ forecasts: action.Forecasts);
+```
+
+This reducer simply sets the `IsLoading` state back to false, and sets the `Forecasts` state to the
+values in the action that was dispatched by our effect.
+
+
diff --git a/Source/Fluxor.sln b/Source/Fluxor.sln
index ba5387ed..4600235c 100644
--- a/Source/Fluxor.sln
+++ b/Source/Fluxor.sln
@@ -35,6 +35,9 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluxorBlazorWeb.CounterSample", "..\Samples\Blazor\01CounterSample\CounterSample\FluxorBlazorWeb.CounterSample.csproj", "{E4E667AC-8CFE-4632-99D0-56DD69117DD2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "02 EffectsSample", "02 EffectsSample", "{730B26CD-CADC-4CF0-8A1D-3BB174692A40}"
+ ProjectSection(SolutionItems) = preProject
+ ..\Samples\Blazor\02EffectsSample\README.md = ..\Samples\Blazor\02EffectsSample\README.md
+ EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluxorBlazorWeb.EffectsSample.Server", "..\Samples\Blazor\02EffectsSample\EffectsSample\Server\FluxorBlazorWeb.EffectsSample.Server.csproj", "{01E13581-FD37-4595-A9DC-37BFAA77864F}"
EndProject