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

Contexts / Environment state #278

Merged
merged 2 commits into from
Mar 10, 2023
Merged

Conversation

JaggerJo
Copy link
Member

@JaggerJo JaggerJo commented Mar 4, 2023

Allows controls to pass state to descendants (similar to swift UI environment objects & react contexts)

@JaggerJo
Copy link
Member Author

JaggerJo commented Mar 4, 2023

Quite impressive how far you get with ~80 LOC which is almost the entire feature.

@JaggerJo
Copy link
Member Author

JaggerJo commented Mar 4, 2023

@JordanMarr @sleepyfran I'm not happy with the naming and the API yet, but it works. Feedback is as always appreciated.

Describe value

[<RequireQualifiedAccess>]
module SharedState =
let brush = EnvironmentState<IBrush>.Create "brush"
let size = EnvironmentState<int>.Create "size"

Provide value

static member main () =
Component (fun ctx ->
let brush = ctx.useState (Brushes.Black :> IBrush, renderOnChange = false)
let size = ctx.useState (2, renderOnChange = false)
EnvironmentStateProvider.create(
state = SharedState.brush,
providedValue = brush,
content = (
EnvironmentStateProvider.create(
state = SharedState.size,
providedValue = size,
content = (
DockPanel.create [
DockPanel.lastChildFill true
DockPanel.background Brushes.White
DockPanel.children [
Views.settings ()
Views.canvas ()
]
]
)
)
)
)
)

Consume value

static member canvas () =
Component.create ("canvas", fun ctx ->
let canvasOutlet = ctx.useState (null, renderOnChange = false)
let isPressed = ctx.useState (false, renderOnChange = false)
let lastPoint = ctx.useState (None, renderOnChange = false)
let brush = ctx.useEnvState (SharedState.brush, renderOnChange = false)
let size = ctx.useEnvState (SharedState.size, renderOnChange = false)

@JaggerJo JaggerJo marked this pull request as draft March 4, 2023 19:29
@sleepyfran
Copy link
Member

Looks good, although I'd maybe make the whole providing a bit more friendly by going full React. If you make the EnvironmentState contain a function that creates the Provider automatically, the providing part would end up like:

SharedState.brush.provider(
             value = brush, 
             content = (
                 ...
             )
)

Consumption-wise I think the API is okay.

I'm not a huge fan of contexts in general because they couple components with an invisible provider that makes it very hard to refactor later (going right now through a very painful transition with them in React at work, so this hits close to home 😆), and I think we have this partially solved with the store pattern that we have in some of the samples, but I also think they won't hurt and it might feel more familiar for people coming from React.

@JordanMarr
Copy link
Collaborator

JordanMarr commented Mar 5, 2023

I haven’t quite recovered from my GitHub session yesterday, but I will look at this in more detail later.

@sleepyfran I recently refactored a React app to use Fable.Store instead of useContext. What is an example of the store pattern you are referring to for FuncUI?

@sleepyfran
Copy link
Member

@JordanMarr for example the Generic Algorithm sample declares this store:

type StateStore =
{ Random: Random
ShowHeatMap: IWritable<bool>
Worlds: IWritable<World list>
Squirrels: IWritable<SimulationResult list>
SelectedSquirrelId: IWritable<Guid option>
SelectedSquirrel: IWritable<SimulationResult option>
SelectedWorldResultIdx: IWritable<int>
SelectedWorldResult: IReadable<IndividualWorldResult option>
SelectedGameStateIdx: IWritable<int>
SelectedGameState: IReadable<GameState option> }

Which are then used as a normal state:

let showHeatMap = ctx.usePassed StateStore.shared.ShowHeatMap
let population = ctx.usePassed StateStore.shared.Squirrels
let selectedId = ctx.usePassed StateStore.shared.SelectedSquirrelId

I'm not familiar with Fable.Store, so not sure if they do something different, but basicaly this just creates a global state that is then shared between all components that reference it. So in order to have something similar to a context, declaring a store like this scoped to a set of components would end up being the same.

@JaggerJo
Copy link
Member Author

JaggerJo commented Mar 5, 2023

Looks good, although I'd maybe make the whole providing a bit more friendly by going full React. If you make the EnvironmentState contain a function that creates the Provider automatically, the providing part would end up like:

SharedState.brush.provider(
             value = brush, 
             content = (
                 ...
             )
)

Consumption-wise I think the API is okay.

Good point 👍 that might be handy.

I'm not a huge fan of contexts in general because they couple components with an invisible provider that makes it very hard to refactor later (going right now through a very painful transition with them in React at work, so this hits close to home 😆), and I think we have this partially solved with the store pattern that we have in some of the samples, but I also think they won't hurt and it might feel more familiar for people coming from React.

I personally think both (global shared state & shared state via contexts) have their place.
If state only starts to exist at some point contexts make a lot of sense.

@sleepyfran
Copy link
Member

If state only starts to exist at some point contexts make a lot of sense.

That's actually a really good point. I guess I've never had this exact use case, but it definitely makes sense to offer this out of the box.

Well, as I said, other than the provider change the rest looks all good to me. Awesome job!

@JaggerJo JaggerJo marked this pull request as ready for review March 10, 2023 20:29
@JaggerJo JaggerJo merged commit 5627ca6 into master Mar 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants