Skip to content

Instantly share code, notes, and snippets.

@JordanMarr
Created June 25, 2020 18:15
Show Gist options
  • Save JordanMarr/29ed7bfd1737dd38eab9eeeae070e6f4 to your computer and use it in GitHub Desktop.
Save JordanMarr/29ed7bfd1737dd38eab9eeeae070e6f4 to your computer and use it in GitHub Desktop.
Fable.React: Updating a parent context from a child component
module Contexts
open Feliz
open Elmish.Toastr
open Elmish
open Domain.Auth
type ToastMessage =
| InfoMsg of string
| SuccessMsg of string
| ErrorMsg of string
type AppState = {
User: Domain.Auth.User
Toast: ToastMessage -> unit
}
let private init =
{ User = { User.ClientId = ""; FirstName = ""; LastName = ""; Username = "" }
Toast = fun msg -> () }
let appContext = React.createContext<AppState>(name = "UserContext", defaultValue = init)
module App
open Elmish
open Feliz
open Feliz.Router
open Fable.React
open Fable.React.Props
open Domain.Auth
open Elmish.Toastr
open Contexts
type State = {
CurrentUrl: string list
IsAuthenticating: bool
User: User option
}
type Msg =
| UrlChanged of string list
| GetUserResponse of Result<UserStatus, string>
| Toast of Cmd<Msg>
let init() =
{ CurrentUrl = []; IsAuthenticating = true; User = None }, Cmd.OfPromise.perform HttpService.getUser () GetUserResponse
let update msg state =
match msg with
| UrlChanged segments -> { state with CurrentUrl = segments }, Cmd.none
| GetUserResponse userResult ->
match userResult with
| Ok userStatus ->
match userStatus with
| Authenticated user -> { state with User = Some user; IsAuthenticating = false }, Cmd.none
| Unauthenticated -> { state with User = None; IsAuthenticating = false }, Cmd.none
| Error err -> { state with User = None; IsAuthenticating = false }, Cmd.none
| Toast toastCmd ->
state, toastCmd
let render state dispatch =
let content =
match state.CurrentUrl with
| [ ]
| [ "home" ] -> HomePage.page()
// ...
| _ -> Html.h1 "Not found"
let content =
match state.User with
| Some user ->
content
| None ->
if state.IsAuthenticating then
h4 [] [ str "Authenticating..." ]
else
// Autodesk Login
a [Href "/signin"] [
button [Class "btn btn-lg btn-default"; Id "autodeskSigninButton"] [
img [Src "https://github.com/Autodesk-Forge/learn.forge.viewhubmodels/raw/master/img/autodesk_text.png"; Style [Height "20"]]
div [] [str "Sign in"]
]
]
let toast (tm: ToastMessage) =
let toastCmd =
match tm with
| InfoMsg msg -> Toastr.message msg |> Toastr.info
| SuccessMsg msg -> Toastr.message msg |> Toastr.success
| ErrorMsg msg -> Toastr.message msg |> Toastr.error
dispatch (Toast toastCmd)
let pageWithUserContext =
match state.User with
| Some user ->
React.contextProvider(Contexts.appContext, { AppState.User = user; AppState.Toast = toast },
Layout.layout {| Content = content; User = state.User; CurrentUrl = state.CurrentUrl |}
)
| None ->
Layout.layout {| Content = content; User = state.User; CurrentUrl = state.CurrentUrl |}
Router.router [
Router.onUrlChanged (UrlChanged >> dispatch)
Router.application pageWithUserContext
]
/// A very simple page that updates App level context (uses useState instead of useReducer or useElmish)
module JobEditPage
open Feliz
open Fable.React
open Fable.React.Props
open Contexts
/// Displays the backup job edit page.
let page = React.functionComponent(fun (props: {| Id: string option |}) ->
let job, setJob = React.useState(blankJob)
let ctx = React.useContext(Contexts.appContext)
// ...
let save() =
promise {
match! HttpService.saveJob job with
| Ok job ->
setJob job
ctx.Toast (SuccessMsg "The backup job was saved.")
| Error err ->
ctx.Toast (ErrorMsg err)
}
|> ignore
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment