Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Rethinking State Management #158

Open
JeroMiya opened this issue Dec 26, 2019 · 5 comments
Open

Rethinking State Management #158

JeroMiya opened this issue Dec 26, 2019 · 5 comments

Comments

@JeroMiya
Copy link

JeroMiya commented Dec 26, 2019

Not sure if this is covered by the framework yet or not (if so it's not documented?) - state management seems a bit cumbersome in the current samples. Requiring the use of State or BindingObject subclasses seems like a lot of boilerplate for something that should be pretty simple. I'd like to see a couple of programming models supported:

Original sample:

	readonly State<int> clickCount = new State<int> (1);

	public MyPage() {
		Body = () => new VStack {
			new Text (() => $"Click Count: {clickCount}"),
			new Button("Update Text", () => {
				clickCount.Value++;
			}
		};
	}

Simplified POCO state management with SetState(Action setter) function:

	int clickCount = 1;
        int secondClickCount = 1;
	public MyPage() {
		Body = () => new VStack {
			new Text (() => $"Click Count: {clickCount}"),
			new Button("Update Text", () => SetState(() => clickCount++)),

                        // StatefulClick is a simple helper method that is the equivalent of the above
                        new Text (() => $"Click Count: {secondClickCount}"),
                        new Button("Update Text").StatefulClick(() => secondClickCount++),

                        new Button("Reset State").StatefulClick(() => {
                                // Set two state fields, only one view re-render
                                clickCount = 1;
                                secondClickCount = 1;
                       }),
		};
	}

External state management with NotifyStateChanged(), equivalent to SetState(() => {}):

public class MyPage : View {

        [Inject]
	public MyAppStateStore Store { get; set; } // perhaps a Redux pattern state management library, or something custom

	public MyPage() {
                Store.MyPageState.Subscribe(state => NotifyStateChanged());
		Body = () => new VStack {
			new Text (() => $"Click Count: {Store.MyPageState.ClickCount}"),
			new Button("Update Text", () => Store.DispatchClickAction()),
		};
	}
}

Going a bit further, if we change Body to take the state as an argument, we can make make this a bit more elegant:

public class MyPage : StatefulView<MyPageState> {

        [Inject]
	public MyAppStateStore Store { get; set; }

	public MyPage() {
                // i.e. StatefulView<TState>.ConnectState(Observable<TState> stateObservable)
                ConnectState(Store.MyPageState); 
		Body = (state => new VStack {
			new Text (() => $"Click Count: {state.ClickCount}"),
			new Button("Update Text", () => Store.DispatchClickAction()),
		});
	}
}

The nice thing about StatefulView<TState> would be that, given the framework is now explicitly aware of a view's state, it can serialize/deserialize it during hot reload to more easily implement a stateful hot reload.

@Clancey
Copy link
Collaborator

Clancey commented Jan 18, 2020

Originally, it was designed similar. I had StatefulView and the Body build passed in the State. You can see it in the old history. However it was removed since this is simpler long term. State is needed for basic data types, if you want to use them directly. And right now, I you can subclass BindingObject for complex objects or implement the interface. This is temporary! Eventually I want you to be able to do:

[BindingObject]
class MyObject
{
public string Foo{get;set;}
}

And that is it! Or maybe just let you subclass an interface?

Or I want some language/runtime changes that would eliminate both! But those would take longer.

@Clancey
Copy link
Collaborator

Clancey commented Jan 18, 2020

Eventually, I may be able to remove State as well using the same techniques! But that is long term. However I still shouldnt need any of the StatefulView<> or State stuff.

Also, [Inject] exists, its just called [Environment], it will grab anything already set. I will add some better examples of that later!

@JeroMiya
Copy link
Author

JeroMiya commented Jan 29, 2020

How does Blazor do it? It seems almost magic. No state wrappers or attributes or interfaces. I'm guessing it just rerenders after any DOM event callback? That might be OK if you could opt-out of it and manage re-renders manually if desired (e.g. for performance).

Would it be possible to just reflect on the view classes themselves and sort of.. make the view or any of its public properties implicitly a [BindingObject] somehow?

@JeroMiya
Copy link
Author

Also, would it be possible to support both models with a separate base class? For instance, if the explicit React/Flutter style SetState(...) method was a better model for an application's state management, could they opt-in to it?

@gpapadopoulos
Copy link

gpapadopoulos commented Jan 8, 2021

[BindingObject]
class MyObject
{
public string Foo {get;set;}
public int ClickCount {get;set;}
public void MyClickMethod() {....}
}

So [BindingObject] binds the whole of MyObject to my View? And how would one reference (and bind to) individual properties from individual controls within my view, such as Foo?

Perhaps via an argument passed to the Body as mentioned above? Such as below:

Body = (state => new VStack {
new Text (() => $"Click Count: {state.ClickCount}"),
new Button("Update Text", () => state.MyClickMethod()),
});

So "state" would be an instance of MyObject? Just a thought

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants