Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

How do I create state nodes that have child nodes with actions? #13

Open
FieldMarshallVague opened this issue Nov 27, 2020 · 20 comments
Open

Comments

@FieldMarshallVague
Copy link

FieldMarshallVague commented Nov 27, 2020

Hi Skclusive,

Sorry if this isn't the right way to ask, but I've been struggling with something. I need to track an object that has child-objects that have their own behaviours. Trunck > Branch > Leaf. They all have their own EditName/EditBranches actions, so I can't use the DTO (snapshot) class as the type in the view layer (razor file)

So, Tree would have a list of Branches, I have Tree as a child of AppState, which is tracked and works properly (since it's on the root). But when I get to adding Branch, this is a child of Tree, not of AppState, so my interface is

ITree {
  List<IBranch> Branches
}

IBranchActions {
  EditName(string)
}

IBranchSnapshot {
  string Name
}

IBranch : IBranchSnapshot, IBranchActions

Now, IBranch needs to have an 'EditName' method, which is part of the Actions interface, this is what is implemented on the Proxy, not the snapshot.

Do you have any examples of this nested structure? I cannot see how to do it and have been circulating for a while now. I've looked at Todo and Flightfinder, but this does not seem to have anything like this. Is it even possible? Will it ever be? Or am I making a mistake?

I thought maybe I just put everything in the root, but you can imagine that would get very messy and tracking the IDs (or whatever) to link them back up would be a nightmare.

I'd really appreciate some advice on this. Thanks!

@skclusive
Copy link
Owner

hi. thanks for trying out state tree. yes. it is possible to define the above nested structure with actions specific to node types. the nesting can go any level.

i just added sample test for your use case here https://github.com/skclusive/Skclusive.Mobx.StateTree/tree/master/test/StateTree.Tests/Models/Nested

and the test here https://github.com/skclusive/Skclusive.Mobx.StateTree/blob/master/test/StateTree.Tests/TestNested.cs

            var root = RootType.Create(new RootSnapshot
            {
                Tree = new TreeSnapshot
                {
                    Branches = new IBranchSnapshot []
                    {
                        new BranchSnapshot { Name = "branch 1" },

                        new BranchSnapshot { Name = "branch 2" }
                    }
                }
            });

            Assert.NotNull(root);

            Assert.NotNull(root.Tree);

            Assert.Equal(2, root.Tree.Branches.Count);

            Assert.Equal("branch 1", root.Tree.Branches[0].Name);

            Assert.Equal("branch 2", root.Tree.Branches[1].Name);

            root.Tree.AddBranch(new BranchSnapshot { Name = "branch 4 (typo)" });

            Assert.Equal(3, root.Tree.Branches.Count);

            Assert.Equal("branch 4 (typo)", root.Tree.Branches[2].Name);

            root.Tree.Branches[2].EditName("branch 3");

            Assert.Equal("branch 3", root.Tree.Branches[2].Name);

hope this helps

@FieldMarshallVague
Copy link
Author

Awesome! Thanks so much for this. I'll test it tomorrow and let you know. Cheers, really helps out.

@FieldMarshallVague
Copy link
Author

FieldMarshallVague commented Dec 1, 2020

Thanks again, that's working for the behaviours, but now I can't treat the objects as the snapshots/dto types. If I pass the Tree (for example) to a component, using root.Tree, the type is ITree (behaviours). How do you access the node as its snapshot type? Obviously there is some merging going on 'under the hood', but how do I cast, say root.Tree.Branches[0] as the BranchSnapShot type?

UPDATE: ahhh, If I use ITree.GetSnapshot() will that return the snapshot with all the data? I'll try that.

Another question, though, the example given does not have nodes that have both data and behaviour. Only the Branch node has this.

If you were to add a Leaf node with data and behaviour (so both Branch and Leaf had both), would that work? That is 90% of my use-case.

UPDATE 2: I have this working now. I added the properties to the IBranch and ILeaf interfaces, which repeated the ones from the snapshot interface. The library 'merges' them and I can retrieve the snapshot using the GetSnapshot method (I only just found this today, but it has made the difference).

Thanks for your help. I'm pretty pleased with this so far.

@skclusive
Copy link
Owner

glad you got the answers yourself :-) someday the verbosity could be avoided using C# code generator.

i have been using it with pretty complex normalised data structure for my own project. recently tested with MobileBlazorBinding (netstandard2.0) as well. working well so far.

@FieldMarshallVague
Copy link
Author

FieldMarshallVague commented Dec 3, 2020

That sounds like a good move forward. I was wondering how we could avoid so much boilerplate, but all redux-style implementations have them (AFAICT). These changes have stopped the Redux debugger working for me, though. It's throwing this error:

Deserialization of interface types is not supported. Type 'IRoot'

This happens when I try to step to a previous state, using the back arrow in the redux debugger. I can see why it's throwing it, but I am not sure what to do about it. That is, where to put the concrete type (Root), or shouldn't this be necessary?

I realised this was due to me not specifying interface->concrete types in the services, so I added

services.TryAddSingleTonEnumerable<JsonConverter, JsonTypeConverter<IBranch, Branch>>().

But then I got this:

The specified type System.Collections.Generic.List1[Demo.IBranch] must derive from the specific value's type Demo.IBranch[].`

So I added:

services.TryAddSingletonEnumerable<JsonConverter, JsonTypeConverter<IList<IBranch>, List<Branch>>>();

But I'm now at the point where I have no idea what the library really wants and where it wants it. I know in the BranchListType it specifies IBranch[], but I do not seem to be able to change that to a list without breaking things. My code is like so:

public readonly static IType<IBranch[], IObservableList<INode, IBranchState>> BranchListType = Types.Late("LateBranchListType", () => Types.List(BranchType));

Could you please give me a pointer?

FYI, before I created the complex object tree, the Redux tool was working well without this StateTreeTool, so I didn't think I needed it.

@FieldMarshallVague
Copy link
Author

FieldMarshallVague commented Dec 3, 2020

Ah, I fixed that by setting the interface types to map to an array (I forgot it was talking to JS):

services.TryAddSingletonEnumerable<JsonConverter, JsonTypeConverter<IEnumerable<IBranch>, Branch[]>>();

And while this doesn't work when I have the shift-alt-D debugger attached (WASM debugger), I can run it without that and it doesn't throw an error until I try to step back in the redux history (similar to before, but different error).

Specified cast is not valid.

blazor.webassembly.js:1 Uncaught (in promise) Error: System.InvalidCastException: Specified cast is not valid. at Skclusive.Mobx.StateTree.ComplexType2[[Demo.IBranchSnapshot[], Demo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[Skclusive.Mobx.Observable.IObservableList2[[Skclusive.Mobx.StateTree.INode, Skclusive.Mobx.StateTree, Version=5.1.2.0, Culture=neutral, PublicKeyToken=null],[Demo.Models.IBranch, Demo.State, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Skclusive.Mobx.Observable, Version=5.1.2.0, Culture=neutral, PublicKeyToken=null]].ApplySnapshot(INode node, Object snapshot)

It doesn't like the casting of IBranch to BranchSnapshot at this point, I think.

@skclusive
Copy link
Owner

you are almost there. we have to only map the snapshot types. not the observable types. as below.

https://github.com/skclusive/Skclusive.Blazor.Samples/blob/master/Skclusive.Blazor.FlightFinder/FlightFinder.State/Extension/FlightFinderExtension.cs#L17

services.TryAddJsonTypeConverter<IRootSnapshot, RootSnapshot>();
services.TryAddJsonTypeConverter<IBranchSnapshot, BranchSnapshot>();
services.TryAddJsonTypeConverter<ITreeSnapshot, TreeSnapshot>();

can you try the above and let me know if that works?

@skclusive
Copy link
Owner

btw i guess you are using the latest version 5.1.2.

@FieldMarshallVague
Copy link
Author

FieldMarshallVague commented Dec 4, 2020

Ah, yes, I already tried that, sorry, the code above was a desperate attempt to see if something else worked :) Sorry, that line was confusing, the error was more clear.

Yes, I'm using the latest versions 5.1.2.

Here is the error again, with some unnecessary bits removed:

Uncaught (in promise) Error: System.InvalidCastException: Specified cast is not valid. at Skclusive.Mobx.StateTree.ComplexType2[[DataTypes.IBranchSnapshot[]],[Skclusive.Mobx.Observable.IObservableList2[[Skclusive.Mobx.StateTree.INode, Skclusive.Mobx.StateTree],[Models.IBranch]], Skclusive.Mobx.Observable]].ApplySnapshot(INode node, Object snapshot) at Skclusive.Mobx.StateTree.ListType2[[DataTypes.IBranchSnapshot, DataTypes],[Models.IBranch, State]].ApplySnapshot(INode node, Object snapshot)
`

It seems to be a problem with my BranchListType, which is defined like so:

public readonly static IType<IBranchSnapshot[], IObservableList<INode, IBranch>> BranchListType = Types.Late("LateBranchListType", () => Types.Optional(Types.List(BranchType), Array.Empty()));

Looking at your examples, I can't see a problem, but maybe I have to do this a different way because it is a more complex structure?

@skclusive
Copy link
Owner

oh. this one. i used to get this error. then i fixed the definition. could not recollect now. will try the same structure and update you.

@FieldMarshallVague
Copy link
Author

Hi Skclusive, did your latest release of the samples have anything in relating to this?

skclusive added a commit that referenced this issue Dec 7, 2020
@skclusive
Copy link
Owner

no. but i just created a new branch with related changes for this issue.

https://github.com/skclusive/Skclusive.Blazor.Samples/tree/Issue13

and this commit e6dd289

i am not able to reproduce the issue. i could time travel in redux devtools with nested structure. may be i miss the use case, have a look and let me know your thoughts.

@FieldMarshallVague
Copy link
Author

FieldMarshallVague commented Dec 8, 2020

Thanks! I'll take a look.

FYI, I can clone the repo now with no problems (not idea why it it didn't work the other times).

@FieldMarshallVague
Copy link
Author

OK, I think I see the problem. I've been using lists on my TreeSnapshot model, so have been declaring the branches as IEnumerable. But this seems to be where the history navigation blows up. It can't cast from the IEnumerable to the IBranchSnapshot[].

FYI, I've also tried just using 'Branchsnapshot' (not the interface), but this didn't help.

Where would I have to override/explicitly cast the type to be able to use IEnumerable? I'm using this model in lists elsewhere, so it's handy to define it as the base.

@skclusive
Copy link
Owner

I guess it is not straight forward. I will have to try with enumeration and update you.

@FieldMarshallVague
Copy link
Author

Ah, thankyou. That would be great.

@FieldMarshallVague
Copy link
Author

FieldMarshallVague commented Dec 15, 2020

Just to be 100% clear, I am using IEnumerable<IBranchsnapshot> instead of IBranchSnapshot[]. I think you understood this, but didn't want to waste your time :)

(github hid the generic part before, because of the angle-bracket syntax)

@FieldMarshallVague
Copy link
Author

FieldMarshallVague commented Jan 8, 2021

Happy new year :) I hope you had a good time.

I wondered if you had any luck in using an enumerable?

@skclusive
Copy link
Owner

Happy new year :-)

Missed the track.

could you please share a minimal repo to reproduce? Or a test case in MobxStateTree ?

@FieldMarshallVague
Copy link
Author

Hi Skclusive, yes, I'll try one or the other and let you know. Cheers.

Repository owner deleted a comment from officialarmannqureshi Feb 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants
@FieldMarshallVague @skclusive and others