adospace / reactorui-maui

MauiReactor is a MVU UI framework built on top of .NET MAUI
MIT License
586 stars 49 forks source link

Component state is not updating #50

Closed phollyer closed 1 year ago

phollyer commented 1 year ago

I am trying to create a button component that transitions to an activity indicator when told to do so by its' parent, but when the parent changes the button's state, it does not carry through to the button's Render func.

I would have thought the following would work, but it appears I'm misunderstanding something...

using System;
using MauiReactor;
using MauiReactor.Compatibility;

namespace MyApp.Controls;

class ButtonXState
{
    public bool IsBusy { get; set; } = false;
}

class ButtonX : Component<ButtonXState>
{
    string text = "Button";

    Func<bool> onClicked;

    public override VisualNode Render()
    {
        Console.WriteLine($"Render => IsBusy: {State.IsBusy}"); // Always false

        if (!State.IsBusy)
        {
             return new Button(text).OnClicked(OnClicked);
        }
        else
        {
             return new ActivityIndicator().IsRunning(true);
        }    
    }

    public ButtonX IsBusy(bool isBusy)
    {
        SetState(s => s.IsBusy = isBusy); // This is not reflected in the Render func

        return this;
    }

    public ButtonX Text(string text_)
    {
        text = text_;

        return this;
    }

    public ButtonX OnClicked(Func<bool> func)
    {
        onClicked = func;

        return this;
    }

    private void OnClicked(object sender, EventArgs args)
    {
        onClicked();
    }
}

And in the parent:

new ButtonX()
    .Text("Register") // Sets Correctly
    .OnClicked(Register) // Fires when clicked
    .IsBusy(State.IsSubmitting) // The correct bool is passed in, but it is not reflected in the Render func 

Am I approaching this all wrong? Would you point me in the right direction?

adospace commented 1 year ago

Hi, yes, I see a few problems with your implementation because you are returning the same component as child component in the render function. I would write something like this (not tested):

namespace MyApp.Controls;

class ButtonX : Component
{
    bool _isBusy;
    Func<bool> _onClicked;
    string _label;

    public ButtonX IsBusy(bool isBusy)
    {
        _isBusy = isBusy;
        return this;
    } 

    public ButtonX OnClicked(Action onClicked)
    {
        _onClicked= onClicked;
        return this;
    } 

    public ButtonX Label(string label)
    {
        _label= label;
        return this;
    } 

    public override VisualNode Render()
    {
        Console.WriteLine($"Render => IsBusy: {_isBusy}"); 

        if (!_isBusy)
        {
             return new Button(_label).OnClicked(_onCLicked);
        }
        else
        {
             return new ActivityIndicator().IsRunning(true);
        }    
    }
}
phollyer commented 1 year ago

Thank you, I will test this later today.

phollyer commented 1 year ago

Yes that works as intended, I thought I had to use SetState in order for the Render function to be called again to update the view.