Single Page Application Demo
This page demonstrates three different approaches to managing the state of a single page application.
spa.get("/", fun req res next ->
let queryColor = req.query?color
let qError =
match req.query?color with
| "red" | "green" | "blue" -> ""
| _ -> "invalid-color"
promise {
let! response =
req
|> gql "query { color { color } }" {||} {| cache = false |}
match response with
| Ok response ->
let gqlColor = response?color?color
SinglePageApplicationDemoPage {| gqlColor = gqlColor; queryColor = queryColor; gqlError = ""; qError = qError |}
|> res.renderComponent
| Error message -> next()
} |> ignore
)
Query String Parameters
This first approach uses the query string parameters of the URL to control the state of the page. This is ideal for sharing links and bookmarks, but it can be difficult to manage the state of complex applications. This approach will also work without JavaScript enabled.
req.FormButton {| baseAction = "/set-color-query"; name = "color"; value = "red"; buttonText = "Red"|}
req.FormButton {| baseAction = "/set-color-query"; name = "color"; value = "green"; buttonText = "Green"|}
req.FormButton {| baseAction = "/set-color-query"; name = "color"; value = "blue"; buttonText = "Blue"|}
req.FormButton {| baseAction = "/set-color-query"; name = "color"; value = "error"; buttonText = "Error"|}
match props.qError with
| "invalid-color" -> Html.p "Invalid color. Please select red, green, or blue."
| _ -> null
spa.post("/set-color-query", fun req res next ->
let color : string = req.body?color
match color with
| "red" | "green" | "blue" ->
res?redirectBackWithNewQuery({| color = color |})
| _ -> res?redirectBackWithNewQuery({| error = "invalid-color" |})
)
Click the buttons to change the color of this text.
Persistent State
This second approach uses a GraphQL mutation in this case to update and persist the state of the page in the user's session data. This could just as easily use a separate CORS-enabled API with REST endpoints backed by a SQL database. This is ideal for complex applications that require a lot of persistant state management. This is another approach that will work without JavaScript enabled.
req.FormButton {| baseAction = "/"; name = "color"; value = "red"; buttonText = "Red"|}
req.FormButton {| baseAction = "/"; name = "color"; value = "green"; buttonText = "Green"|}
req.FormButton {| baseAction = "/"; name = "color"; value = "blue"; buttonText = "Blue"|}
req.FormButton {| baseAction = "/"; name = "color"; value = "error"; buttonText = "Error"|}
match props.gqlError with
| "invalid-color" -> Html.p "Invalid color. Please select red, green, or blue."
| _ -> null
spa.post("/", fun req res next ->
let color : string = req.body?color
promise {
let! response =
req
|> gql "mutation ($color: String) { setColor(color: $color) { success } }"
{| color = color |} {||}
match response with
| Ok response ->
res.redirectBackWithNewQuery()
| Error message ->
SinglePageApplicationDemoPage ({| gqlColor = ""; queryColor = ""; gqlError = message; qError = "" |})
|> res.renderErrorComponent
} |> ignore
)
Hello, null
Click the buttons to change the color of this text.
Temporary State
This third approach uses a useStore hook to manage the state of the page. This is ideal for complex applications that don't need to track or persist parts of the state of the application. Since this is client-side only, it's not ideal for sharing links or bookmarks nor will it work without JavaScript enabled. Temporary state is best used to set up the conditions for creating persistent state.
let (stateColor, setStateColor) = React.useState ""
Html.button [ prop.onClick (fun _ -> setStateColor "red"); prop.text "Red" ]
Html.button [ prop.onClick (fun _ -> setStateColor "green"); prop.text "Green" ]
Html.button [ prop.onClick (fun _ -> setStateColor "blue"); prop.text "Blue" ]
Html.button [ prop.onClick (fun _ -> setStateColor "error"); prop.text "Error" ]
match stateColor with
| "error" -> Html.p "Invalid color. Please select red, green, or blue."
| _ -> null
Click the buttons to change the color of this text.
Together the three approaches demonstrate the flexibility of Fex to handle different state management needs and can be mixed and matched depending on then needs of the single page application. We'll see more of this approach in the next section.
Next: Single Page Application Advanced Demo