Skip to content

Commit 8d01993

Browse files
committed
Remove unneeded warnings about pinned versions; install UseElmish using Femto; align with new Todo structure
1 parent 5d32c7b commit 8d01993

1 file changed

Lines changed: 91 additions & 82 deletions

File tree

docs/recipes/ui/routing-with-elmish.md

Lines changed: 91 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,17 @@ In this recipe we add routing to a safe app, and implement the todo list page us
88

99
## 1. Installing dependencies
1010

11-
!!! warning "Pin Fable.Core to V3"
12-
At the time of writing, the published version of the SAFE template does not have the version of `Fable.Core` pinned; this can create problems when installing dependencies.
13-
14-
If you are using version v.4.2.0 of the template, pin `Fable.Core` to version 3 in `paket.depedencies` at the root of the project
15-
16-
```.diff title="paket.dependencies"
17-
...
18-
-nuget Fable.Core
19-
+nuget Fable.Core ~> 3
20-
...
21-
```
22-
23-
2411
Install Feliz.Router in the Client project
2512

2613
```bash
27-
dotnet paket add Feliz.Router -p Client -V 3.8
14+
dotnet paket add Feliz.Router -p Client
2815
```
2916

30-
!!! Warning "Feliz.Router versions"
31-
At the time of writing, the current version of the SAFE template (4.2.0) does not work well with the latest version of Feliz.Router (4.0).
32-
To work around this, we install Feliz.Router 3.8, the latest version that works with SAFE template version 4.2.0.
33-
34-
If you are working with a newer version of the SAFE template, it might be worth trying to install the newest version of Feliz.Router.
35-
To see the installed version of the SAFE template, run in the command line:
36-
37-
```bash
38-
dotnet new --list
39-
```
40-
4117
Install Feliz.UseElmish in the Client project
4218

4319
```bash
44-
dotnet paket add Feliz.UseElmish -p client
20+
cd src/Client
21+
dotnet femto install Feliz.UseElmish
4522
```
4623

4724
Open the router in the client project
@@ -58,10 +35,10 @@ Create a new Module `TodoList` in the client project. Move the following functio
5835
* Msg
5936
* todosApi
6037
* init
61-
* update
62-
* containerBox
38+
* todoAction
39+
* todoList
6340

64-
Also open `Shared`, `Fable.Remoting.Client`, `Elmish`, `Feliz.Bulma` and `Feliz`.
41+
Also open `Shared`, `Fable.Remoting.Client`, `Elmish` and `Feliz`.
6542

6643
```fsharp title="TodoList.fs"
6744
module TodoList
@@ -70,7 +47,6 @@ open Shared
7047
open Fable.Remoting.Client
7148
open Elmish
7249
73-
open Feliz.Bulma
7450
open Feliz
7551
7652
type Model = { Todos: Todo list; Input: string }
@@ -86,13 +62,12 @@ let todosApi =
8662
|> Remoting.withRouteBuilder Route.builder
8763
|> Remoting.buildProxy<ITodosApi>
8864
89-
let init () : Model * Cmd<Msg> =
65+
let init () =
9066
let model = { Todos = []; Input = "" }
9167
let cmd = Cmd.OfAsync.perform todosApi.getTodos () GotTodos
92-
9368
model, cmd
9469
95-
let update (msg: Msg) (model: Model) : Model * Cmd<Msg> =
70+
let update msg model =
9671
match msg with
9772
| GotTodos todos -> { model with Todos = todos }, Cmd.none
9873
| SetInput value -> { model with Input = value }, Cmd.none
@@ -102,43 +77,57 @@ let update (msg: Msg) (model: Model) : Model * Cmd<Msg> =
10277
let cmd = Cmd.OfAsync.perform todosApi.addTodo todo AddedTodo
10378
10479
{ model with Input = "" }, cmd
105-
| AddedTodo todo -> { model with Todos = model.Todos @ [ todo ] }, Cmd.none
106-
107-
let containerBox (model: Model) (dispatch: Msg -> unit) =
108-
Bulma.box [
109-
Bulma.content [
110-
Html.ol [
111-
for todo in model.Todos do
112-
Html.li [ prop.text todo.Description ]
80+
| AddedTodo todo ->
81+
{
82+
model with
83+
Todos = model.Todos @ [ todo ]
84+
},
85+
Cmd.none
86+
87+
let private todoAction model dispatch =
88+
Html.div [
89+
prop.className "flex flex-col sm:flex-row mt-4 gap-4"
90+
prop.children [
91+
Html.input [
92+
prop.className
93+
"shadow appearance-none border rounded w-full py-2 px-3 outline-none focus:ring-2 ring-teal-300 text-grey-darker"
94+
prop.value model.Input
95+
prop.placeholder "What needs to be done?"
96+
prop.autoFocus true
97+
prop.onChange (SetInput >> dispatch)
98+
prop.onKeyPress (fun ev ->
99+
if ev.key = "Enter" then
100+
dispatch AddTodo)
101+
]
102+
Html.button [
103+
prop.className
104+
"flex-no-shrink p-2 px-12 rounded bg-teal-600 outline-none focus:ring-2 ring-teal-300 font-bold text-white hover:bg-teal disabled:opacity-30 disabled:cursor-not-allowed"
105+
prop.disabled (Todo.isValid model.Input |> not)
106+
prop.onClick (fun _ -> dispatch AddTodo)
107+
prop.text "Add"
113108
]
114109
]
115-
Bulma.field.div [
116-
field.isGrouped
117-
prop.children [
118-
Bulma.control.p [
119-
control.isExpanded
120-
prop.children [
121-
Bulma.input.text [
122-
prop.value model.Input
123-
prop.placeholder "What needs to be done?"
124-
prop.onChange (fun x -> SetInput x |> dispatch)
125-
]
126-
]
127-
]
128-
Bulma.control.p [
129-
Bulma.button.a [
130-
color.isPrimary
131-
prop.disabled (Todo.isValid model.Input |> not)
132-
prop.onClick (fun _ -> dispatch AddTodo)
133-
prop.text "Add"
134-
]
110+
]
111+
112+
[<ReactComponent>]
113+
let todoList model dispatch =
114+
Html.div [
115+
prop.className "bg-white/80 rounded-md shadow-md p-4 w-5/6 lg:w-3/4 lg:max-w-2xl"
116+
prop.children [
117+
Html.ol [
118+
prop.className "list-decimal ml-6"
119+
prop.children [
120+
for todo in model.Todos do
121+
Html.li [ prop.className "my-1"; prop.text todo.Description ]
135122
]
136123
]
124+
125+
todoAction model dispatch
137126
]
138127
]
139128
```
140129

141-
## 4. Add the UseElmish hook to the TodoList Module
130+
## 4. Add the UseElmish hook to the TodoList view function
142131

143132
open Feliz.UseElmish in the TodoList Module
144133

@@ -147,21 +136,21 @@ open Feliz.UseElmish
147136
...
148137
```
149138

150-
In the todoList module, rename `containerBox` to `view`.
151-
On the first line, call `React.useElmish` passing it the `init` and `update` functions. Bind the output to `model` and `dispatch`
139+
In the todoList module, rename the function `todoList` to `view`, and remove the `private` access modifier.
140+
On the first line, call `React.useElmish`, passing it the `init` and `update` functions. Bind the output to `model` and `dispatch`
152141

153142
=== "Code"
154143
```fsharp title="TodoList.fs"
155-
let view (model: Model) (dispatch: Msg -> unit) =
156-
let model, dispatch = React.useElmish(init, update, [||])
144+
let view model dispatch =
145+
let model, dispatch = React.useElmish (init, update, [||])
157146
...
158147
```
159148

160149
=== "Diff"
161150
```.diff title="TodoList.fs"
162-
-let containerBox (model: Model) (dispatch: Msg -> unit) =
163-
+let view (model: Model) (dispatch: Msg -> unit) =
164-
+ let model, dispatch = React.useElmish(init, update, [||])
151+
-let containerBox model dispatch =
152+
+let view model dispatch =
153+
+ let model, dispatch = React.useElmish (init, update, [||])
165154
...
166155
```
167156

@@ -176,7 +165,7 @@ Replace the arguments of the function with unit, and add the `ReactComponent` at
176165
=== "Diff"
177166
```.diff title="Index.fs"
178167
+ [<ReactComponent>]
179-
- let view (model: Model) (dispatch: Msg -> unit) =
168+
- let view model dispatch =
180169
+ let view () =
181170
...
182171
```
@@ -213,9 +202,7 @@ let initFromUrl url =
213202
Create a new `init` function, that fetches the current url, and calls initFromUrl.
214203

215204
```fsharp title="Index.fs"
216-
let init () =
217-
Router.currentUrl ()
218-
|> initFromUrl
205+
let init () = Router.currentUrl () |> initFromUrl
219206
```
220207
## 7. Updating the Page
221208

@@ -228,33 +215,55 @@ type Msg =
228215
Add an `update` function, that reinitializes the app based on an URL
229216

230217
```fsharp title="Index.fs"
231-
let update (msg: Msg) (model: Model) : Model * Cmd<Msg> =
218+
let update msg model =
232219
match msg with
233-
| PageChanged url ->
234-
initFromUrl url
220+
| PageChanged url -> initFromUrl url
235221
```
236222

237223
## 8. Displaying pages
238224

239-
Add a containerBox function to the `Index` module, that returns the appropriate page content
225+
Add a pageContent function to the `Index` module, that returns the appropriate page content
240226

241227
```fsharp title="Index.fs"
242-
let containerBox (model: Model) (dispatch: Msg -> unit) =
228+
let pageContent model =
243229
match model.CurrentPage with
244-
| NotFound -> Bulma.box "Page not found"
230+
| NotFound -> Html.text "Page not found"
245231
| TodoList -> TodoList.view ()
246232
```
233+
234+
In the `view` function, replace the call to `todoList` with a call to `pageContent`
235+
236+
=== "Code"
237+
```
238+
let view model dispatch =
239+
Html.section [
240+
...
241+
pageContent model
242+
...
243+
]
244+
```
245+
=== "Diff"
246+
```
247+
let view model dispatch =
248+
Html.section [
249+
...
250+
- todoList view model
251+
+ pageContent model
252+
...
253+
]
254+
```
255+
247256
## 9. Add the router to the view
248257

249258
Wrap the content of the view method in a `React.Router` element's router.children property, and add a `router.onUrlChanged` property to dispatch the urlChanged message
250259

251260
=== "Code"
252261
```fsharp title="Index.fs"
253-
let view (model: Model) (dispatch: Msg -> unit) =
262+
let view model dispatch =
254263
React.router [
255264
router.onUrlChanged ( PageChanged>>dispatch )
256265
router.children [
257-
Bulma.hero [
266+
Html.section [
258267
...
259268
]
260269
]
@@ -266,7 +275,7 @@ Wrap the content of the view method in a `React.Router` element's router.childre
266275
+ React.router [
267276
+ router.onUrlChanged ( PageChanged>>dispatch )
268277
+ router.children [
269-
Bulma.hero [
278+
Html.section [
270279
...
271280
]
272281
+ ]

0 commit comments

Comments
 (0)