BlazorHooked 0.1.0-alpha2

This is a prerelease version of BlazorHooked.
There is a newer version of this package available.
See the version list below for details.
dotnet add package BlazorHooked --version 0.1.0-alpha2                
NuGet\Install-Package BlazorHooked -Version 0.1.0-alpha2                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BlazorHooked" Version="0.1.0-alpha2" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add BlazorHooked --version 0.1.0-alpha2                
#r "nuget: BlazorHooked, 0.1.0-alpha2"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install BlazorHooked as a Cake Addin
#addin nuget:?package=BlazorHooked&version=0.1.0-alpha2&prerelease

// Install BlazorHooked as a Cake Tool
#tool nuget:?package=BlazorHooked&version=0.1.0-alpha2&prerelease                

Hooked on Blazor

Nuget

A minimal boiler-plate, state management framework for Blazor that resembles React Hooks. It also enables a slightly more functional approach to Blazor components by being a bit more forceful with immutability.

Installation

Install from Nuget NuGet Status

Add the obligitory using statement to _Imports.razor if you don't want @usings in every component.

@using BlazorHooked

Hooks

Hooks are accessed via a HookContext which is provided by the Hook component. HookContext is scoped within a Hook, so you can have multiple Hooks within an application or even component if you really wanted too.

<Hook>
    @{
        context.UseX();
    }
    <div>Hello</div>
</Hook>

You can rename the context to something more helpful and/or to avoid collisions.

<Hook Context="Hook">
    @{
        Hook.UseX();
    }
    <div>Hello</div>
</Hook>

UseState

UseState takes an initial value for the state and returns a tuple, the first item being the current state, and the second being a function to update the state AKA the set function. You should treat the state as immutable and only update it via the set function.

You would typically only really use UseState for small value types. The most simple usecase would be a counter:

<Hook>
    @{
        var (count, setCount) = context.UseState(0);
    }

    <p>Count: @count</p>
    <p>
        <button @onclick=@(() => setCount(count + 1))>Up</button>
        <button @onclick=@(() => setCount(count - 1))>Down</button>
    </p>
</Hook>

UseReducer

This is yet another Flux like thing, but Hooks make it so much simpler. It acts much like UseState but can handle more granular updates to the state via Actions. Like UseState it takes an initial state but also a Reducer. Again like UseState it returns the current state, but the set function is replaced by a Dispatcher.

Lets define some of those words:

  • Action: A command or event to inform the reducer that the state should be changed. These are usually best served by records.
  • Reducer: A function that takes the current state and an action and returns the new state. The framework doesn't care how you do the reduction; it could be a local function, a static method on a seperate class or a type that you inject. It's best practice, however, to not trigger things like http requests from the reducer, that's what Effects are for.
  • Dispatcher: A function provided by the framework that you call with an Action to invoke the Reducer.

Lets refactor our counter:

@code {
    public record CounterState(int Count);

    public record Increment();
    public record Decrement();

    private CounterState Reducer(CounterState state, object action) => action switch
    {
        Increment => state with { Count = state.Count + 1 },
        Decrement => state with { Count = state.Count - 1 },
        _ => state,
    };
}

<Hook>
    @{
        var (state, dispatch) = context.UseReducer(Reducer, new CounterState(0));
    }

    <p>Count: @state!.Count</p>
    <p>
        <button @onclick=@(() => dispatch(new Increment()))>Up</button>
        <button @onclick=@(() => dispatch(new Decrement()))>Down</button>
    </p>
</Hook>

That's obviously a trivial example, but it gives you the idea of all you need. A common use for a reducer is to track an async request. In-fact is so common that to save you some more boiler plate, the Loader component is built in.

<Loader Load=@LoadData T="object">
    <Loading>
        <p><em>Loading...</em></p>
    </Loading>
    <Loaded Context="data">
        @data
    </Loaded>
    <Failed>
        <p><em>Uhoh...</em></p>
    </Failed>
</Loader>

@code {
    private async Task LoadData(Dispatch dispatch)
    {
        try
        {
            var data = await SomeAsyncService();

            dispatch(new LoaderActions.Loaded<object>(data));
        }
        catch (Exception ex)
        {
            dispatch(new LoaderActions.Failed(ex));
        }
    }
}

UseEffect

Effects are used to start background tasks and clean up after them when they're finished with. The classic example would be to start listening on a websocket when the component is first rendered, then gracefully shutdown the connection when the component is unmounted and disposed. If your Effect uses a variable like a value from UseState or a component paramenter and you'd like the Effect to re-run when that changes, you add that variable as a Dependency by letting UseEffect track it's value when you define the Effect.

@inject WebSocketService UserNotificationService

@code {
    [Parameter]
    public Guid UserId { get; set; }

    public Func<Func<Task>> ListenForUserNotifications(SetState<string[]> setUserMessages) => async () =>
    {
        await UserNotificationService.StartListening(UserId, setUserMessages);

        return async () => await UserNotificationService.StopListening(UserId);
    };
}

<Hook>
    @{
        var (messages, setMessages) = context.UseState(new string[0]);

        context.UseEffect(ListenForUserNotifications(setMessages), new object[] { this.UserId });
    }

    @foreach(var message in messages)
    {
        <p>@message</p>
    }
</Hook>
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.3.2 499 2/16/2022
0.3.1 420 2/13/2022
0.3.0 405 2/12/2022
0.2.0 422 2/8/2022
0.1.6 435 2/2/2022
0.1.5 458 2/2/2022
0.1.4 439 2/2/2022
0.1.3 419 2/1/2022
0.1.2 424 2/1/2022
0.1.1 427 2/1/2022
0.1.0-alpha2 187 1/30/2022
0.1.0-alpha1 167 1/30/2022