ProductiveRage / Bridge.React

Bindings for Bridge.NET for React - write React applications in C#!
MIT License
74 stars 14 forks source link

Add support for SetState to accept an updater function #30

Closed dionrhys closed 7 years ago

dionrhys commented 7 years ago

React allows setting a component's state by either merging a new state object with the existing state object or by replacing the state using an updater function that derives new state from previous state.

Without this updater function support, it's not possible to have multiple calls to SetState happen properly in Bridge.React because the second one would always need to rely on outdated state (instead of state that is currently batched up for updating). Bridge.React doesn't support merging state, so new state would override any state changes currently batched up by a previous SetState call.

The new call can be done like so:

SetState((prevState, props) => prevState.WithDropdownOpen(true));

This ensures that new state changes derive from state change calls that have already happened.

This is all part of the official React API for setState(): https://facebook.github.io/react/docs/react-component.html#setstate

ProductiveRage commented 7 years ago

I tried to produce a test case to prove that this does what it says by using a component like this:

public sealed class TestComponent : Component<object, TestComponent.State>
{
    public TestComponent() : base(null) { }

    protected override State GetInitialState()
    {
        Window.SetTimeout(
            () =>
            {
                Console.WriteLine("SetState");
                SetState(state.With(_ => _.Value, value => value + 1));
                SetState(state.With(_ => _.Value, value => value + 1));
            },
            1000
        );
        return new State(1);
    }

    public override ReactElement Render()
    {
        return DOM.Div("Value: " + state.Value);
    }

    public sealed class State : IAmImmutable
    {
        public State(int value)
        {
            this.CtorSet(_ => _.Value, value);
        }
        public int Value { get; }
    }
}

But, unfortunately, that doesn't trigger the update batching logic - which is apparent because it correctly updates to show "Value: 3" instead of "Value: 2" (which is what it would show if the state variable was out of date in the second delayed SetState call). I guess that there must be more demanding work required to trigger batching of updates.. but I've been unable to find any information about how I could trigger it for testing purposes.

This is a pity but needn't be a blocker. I've read up on the linked documentation and I think the really important line is this one:

Both prevState and props received by the updater function are guaranteed to be up-to-date

It's worth remarking on this line:

The output of the updater is shallowly merged with prevState

With the way that Bridge.React has to work, shallow-merging is not supported and so it will be a full replacement of the state value. However, this is to be expected as it is consistent with the existing SetState method signature.