An open API service indexing awesome lists of open source software.

https://github.com/byronmayne/recoil.net

Recoil.net is the C# version of the Facebook's Recoil.js library. It's designed to handle managing state in WPF applications.
https://github.com/byronmayne/recoil.net

Last synced: 2 days ago
JSON representation

Recoil.net is the C# version of the Facebook's Recoil.js library. It's designed to handle managing state in WPF applications.

Awesome Lists containing this project

README

          

# Recoil.net

Recoil.net is the C# version of the Facebook's [Recoil.js](https://recoiljs.org/) library. It's designed to handle managing state in WPF applications. There are 3 main goals this library is intended to do.

#### Flexible shared state
- The ability to have different things in syncs in sync in different parts of the application.

#### Derived data and queries
- The ability to compute things based on changing state efficiently in a way that is very robust so that we can move quickly and not have bugs.

#### App-wide state observation
- For things like time travel debugging, undo support, persistence logging etc.
- Ability to view what is going on across the whole application

## Getting Started

### Creating the Root

The first thing that needs to be added to the root if your WPF application in the ``. This is where all the data will be stored for your application. In most cases you will only need a single one but multiple is supported.

```xml



```

Inside the `` an instance of the `IRecoilStore` will be created. This is where all children will store their values. Whenever you create a new `RecoilState` object it will walk the hierarchy and use the first root it finds. If none is found an exception will be thrown.

### Defining State

With Recoil all state is defined using one of two types `Atom` or `Selector`. These Recoil value objects are keys to data that exists in the `IRecoilStore` and each state object *must* have a unique key.

```csharp
public static class UserState
{
public static readonly Atom FirstName;
public static readonly Atom LastName;
public static readonly Selector Username { get; }

static UserState
{
// new Atom(string: key, defaultValue: T | Atom | Selector)
FirstName = new Atom("User.FirstName", "John");
LastName = new Atom("User.LastName", "Smith");

// new Selector(string: key, ISelectorBuilder:get)
Username = new Selector("User.Username", GetUsername);
}

private static string GetUsername(ISelectorBuilder get)
=> $"{await get.GetAsync(FirstName)}_{await get.GetAsync(LastName)}";
}
```
Here we have defined two `Atom` of state with default values. Later in this demo we will use these keys to get and fetch data.

The more interesting one is the `Selector Username`. Selectors are derived state and will update whenever one of it's dependencies changes values. In this case if we change the `FirstName` or `LastName` of the user the value of the `Username` property will be updated to reflect these changes. Selectors can also depend on other selectors allowing you to build complex objects dependency trees.

### Reading Values

`Atom` and `Selectors` don't actually store any values themselves instead they are used as keys to access values in the `IRecoilStore` instance. To actually get the value we need to create some state.

There is two ways of going about reading values from Recoil.

### Code Behind

You don't have to use View Models to use Recoil instead you can just bind data in the code behind.

First we have to define the properties that we want to use. We create a `RecoilState` value holder for each property. The then initialize them in our constructor using the keys (Atom/Selector) that we crated in the first step.

```csharp
public class ProfileControl : UserControl
{
public RecoilState Username { get; }
public RecoilState FirstName { get; }
public RecoilState LastName { get; }

public ProfileControl()
{
Username = this.UseRecoilState(UserState.Username);
FirstName = this.UseRecoilState(UserState.LastName);
LastName = this.UseRecoilState(UserState.LastName);

// Initialize component must be called after creating the state
// otherwise an exception will be thrown telling you to fix it.
InitializeComponent();
}
}
```
You might have noticed that we don't need implement a `DependencyProperty` or the `INotifyPropertyChanged` interface. This is because each `RecoilState` object already has it's own implementation. This is the reason `InitializeComponent()` has to be invoked last because if it was invoked before the `RecoilState` objects would be null and no binding would be created.

After we have these setup we can then bind our data to our view

```xml


First Name:

Last Name:


```

Now whenever the user updates `FirstName` or `LastName` the text block will update with their username value.

### View Models

To use a view model we use the extension method `UseRecoilViewModel` of `FrameworkElement`.

```xml
public class ProfileControl : UserControl
{
public ProfileControl()
{
this.UseRecoilViewModel();
InitializeComponent();
}
}
```
This function does the following.
1. Checks if the FrameworkElement is loaded
1. No: go to step 2
2. Yes: go to step 3
2. Subscribes step 3 to `FrameworkElement.OnLoaded`.
3. Walk the wpf hierarchy and finds the closes `RecoilRoot`
4. Grabs the `RecoilRoot.Store`
5. Invokes the factory method to produce the view model
6. Assigns the `FrameworkElement.DataContext` value.

All the being said is because the `DataContext` value won't always be read after invoking this method

Here is the definition of the view model with the required constructor.

```csharp
// The view model we want to bind data to
public class ProfileViewModel
{
public RecoilState Username { get; }
public RecoilState FirstName { get; }
public RecoilState LastName { get; }

public ProfileViewModel(IRecoilStore store)
{
Username = store.UseState(UserState.Username);
FirstName = store.UseState(UserState.FirstName);
LastName = store.UseState(UserState.LastName);
}
}
```
It's the same case with using code behind where we don't have to use `INotifyPropertyChanged` to get the values to update with binding.

## More Information

* [Atoms](./Docs/Atoms.md)
* [Selectors](./Docs/Selectors.md)