Skip to content

Commit b625b8e

Browse files
authored
Merge pull request #697 from mjblacker/UseElmish-SSG
Fix: Feliz.Elmish SSG
2 parents 347ae94 + 564bc0d commit b625b8e

File tree

6 files changed

+25
-31
lines changed

6 files changed

+25
-31
lines changed

docs/docs/ecosystem/04_Hooks/Feliz.UseElmish.mdx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,14 @@ or
2424
dotnet femto install Feliz.UseElmish
2525
```
2626

27-
:::danger
28-
Unable to show live example because Feliz.UseElmish does not support Server-Side Rendering (SSR). Help me out by contributing a PR if you need this feature.
29-
:::
30-
3127
Here is an example to demonstrate how to build such component:
3228

29+
import ElmishCounter from '../../feliz-docs/fableoutput/Examples/React/ElmishCounter'
3330
import RawElmishCounter from '!!raw-loader!../../feliz-docs/Examples/React/ElmishCounter.fs'
3431

35-
<CodeBlock language="fsharp" showLineNumbers>
36-
{RawElmishCounter}
37-
</CodeBlock>
32+
<ComponentRender code={RawElmishCounter}>
33+
<ElmishCounter />
34+
</ComponentRender>
3835

3936
The difference here from a full-fledged Elmish applications is that there isn't an "Elmish entry point" to run the component and manage its life-cycle. Instead, the `React.useElmish` hooks manages the Elmish life-cycle internally within the React component so that it can run standalone inside other React components:
4037

@@ -113,12 +110,12 @@ ReactDOM.render(App(), document.getElementById "feliz-app")
113110

114111
Next, let's combine this hook with other React hooks such as `React.useState` and `React.useEffect`:
115112

113+
import ElmishCounterSubscription from '../../feliz-docs/fableoutput/Examples/React/ElmishCounterSubscription'
116114
import RawElmishCounterSubscription from '!!raw-loader!../../feliz-docs/Examples/React/ElmishCounterSubscription.fs'
117115

118-
<CodeBlock language="fsharp" >
119-
{RawElmishCounterSubscription}
120-
</CodeBlock>
121-
116+
<ComponentRender code={RawElmishCounterSubscription}>
117+
<ElmishCounterSubscription />
118+
</ComponentRender>
122119

123120
### Disposing of resources
124121

src/Feliz.UseElmish/Feliz.UseElmish.fsproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,7 @@
1616
<ItemGroup>
1717
<PackageReference Include="Fable.Elmish"/>
1818
</ItemGroup>
19+
<ItemGroup>
20+
<ProjectReference Include="..\Feliz\Feliz.fsproj" />
21+
</ItemGroup>
1922
</Project>

src/Feliz.UseElmish/UseElmish.fs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
11
module Feliz.UseElmish
22

3+
open System
34
open Fable.Core
45
open Elmish
56

67
module private Util =
7-
type UseSyncExternalStoreSubscribe = delegate of (unit -> unit) -> (unit -> unit)
8-
9-
[<ImportMember("react")>]
10-
let useSyncExternalStore(subscribe: UseSyncExternalStoreSubscribe, getSnapshot: unit -> 'Model, getServerSnapshot: (unit -> 'Model) option): 'Model = jsNative
11-
12-
[<ImportMember("react")>]
13-
let useState(init: unit -> 'State): 'State * ('State -> unit) = jsNative
14-
15-
[<ImportMember "react">]
16-
let useEffect(effect: unit -> unit, dependencies: obj array) : unit = jsNative
178

189
[<Emit "setTimeout($0)">]
1910
let setTimeout(callback: unit -> unit) : unit = jsNative
@@ -105,21 +96,22 @@ module private Util =
10596
member _.IsOutdated(arg', dependencies') = arg <> arg' || dependencies <> dependencies'
10697

10798
open Util
99+
open Feliz
108100

109101
[<Erase>]
110102
type React =
111103
static member useElmish(program: unit -> Program<'Arg, 'Model, 'Msg, unit>, arg: 'Arg, ?dependencies: obj array): 'Model * ('Msg -> unit) =
112-
let state, setState = useState(fun () -> ElmishState(program, arg, dependencies))
104+
let state, setState = React.useState(fun () -> ElmishState(program, arg, dependencies))
113105
if state.IsOutdated(arg, dependencies) then
114106
ElmishState(program, arg, dependencies) |> setState
115-
let finalState, dispatch, subscribed, queuedMessages = useSyncExternalStore(state.Subscribe, (fun () -> state.State), None)
107+
let finalState, dispatch, subscribed, queuedMessages = React.useSyncExternalStore(state.Subscribe, UseSyncExternalStoreSnapshot(fun () -> state.State), UseSyncExternalStoreSnapshot(fun () -> state.State))
116108
// Run any queued messages that were dispatched before the Elmish program finished subscribing
117-
useEffect((fun () ->
109+
React.useEffect((fun () ->
118110
if subscribed && queuedMessages.Count > 0 then
119111
for msg in queuedMessages do
120112
setTimeout(fun () -> dispatch msg)
121113
queuedMessages.Clear()
122-
), [| subscribed; queuedMessages |])
114+
), [| box subscribed; box queuedMessages |])
123115
finalState, dispatch
124116

125117
static member inline useElmish(program: unit -> Program<unit, 'Model, 'Msg, unit>, ?dependencies: obj array) =

src/Feliz/React/React.fs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -636,25 +636,25 @@ useLayoutEffect(() => {
636636
/// <param name='getSnapshot'>A function that returns the current value of the external data source.</param>
637637
/// <returns>The current value from the external data source.</returns>
638638
[<ImportMember("react")>]
639-
static member inline useSyncExternalStore(subscribe: Func<(unit -> unit),(unit -> unit)> , getSnapshot: unit -> 'T, ?getServerSnapshot: unit -> 'T): 'T = jsNative
639+
static member inline useSyncExternalStore(subscribe: UseSyncExternalStoreSubscribe, getSnapshot: UseSyncExternalStoreSnapshot<'T>, ?getServerSnapshot: UseSyncExternalStoreSnapshot<'T>): 'T = jsNative
640640
641641
/// <summary>
642642
/// Subscribes to a data source, and returns the current value from it.
643643
/// </summary>
644644
/// <param name='subscribe'>A function that sets up a subscription to the external data source. It receives a callback to be called when the data source changes.</param>
645645
/// <param name='getSnapshot'>A function that returns the current value of the external data source.</param>
646646
/// <returns>The current value from the external data source.</returns>
647-
static member inline useSyncExternalStore(subscribe: (unit -> unit) -> (unit -> unit), getSnapshot: unit -> 'T, ?getServerSnapshot: unit -> 'T): 'T =
648-
React.useSyncExternalStore( Func<_,_> subscribe, getSnapshot, ?getServerSnapshot = getServerSnapshot)
647+
static member inline useSyncExternalStore(subscribe: (unit -> unit) -> (unit -> unit), getSnapshot: UseSyncExternalStoreSnapshot<'T>, ?getServerSnapshot: UseSyncExternalStoreSnapshot<'T>): 'T =
648+
React.useSyncExternalStore( UseSyncExternalStoreSubscribe subscribe, getSnapshot, ?getServerSnapshot = getServerSnapshot)
649649
650650
/// <summary>
651651
/// Subscribes to a data source, and returns the current value from it.
652652
/// </summary>
653653
/// <param name='subscribe'>A function that sets up a subscription to the external data source. It receives a callback to be called when the data source changes.</param>
654654
/// <param name='getSnapshot'>A function that returns the current value of the external data source.</param>
655655
/// <returns>The current value from the external data source.</returns>
656-
static member inline useSyncExternalStore(subscribe: (unit -> unit) -> #IDisposable, getSnapshot: unit -> 'T, ?getServerSnapshot: unit -> 'T): 'T =
657-
React.useSyncExternalStore( Func<_,_>
656+
static member inline useSyncExternalStore(subscribe: (unit -> unit) -> #IDisposable, getSnapshot: UseSyncExternalStoreSnapshot<'T>, ?getServerSnapshot: UseSyncExternalStoreSnapshot<'T>): 'T =
657+
React.useSyncExternalStore( UseSyncExternalStoreSubscribe
658658
(fun (callback) ->
659659
let disp = subscribe(callback)
660660
fun () -> disp.Dispose()

src/Feliz/React/ReactTypes.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ module ReactTypes =
4242

4343
type ReactNode = U6<ReactElement, seq<ReactElement>, string, float, int, bool>
4444

45+
type UseSyncExternalStoreSubscribe = delegate of (unit -> unit) -> (unit -> unit)
46+
type UseSyncExternalStoreSnapshot<'T> = delegate of unit -> 'T
47+
4548
[<Erase>]
4649
type IReactRoot =
4750
/// Renders the provided React element into the DOM in the supplied container.

tests/Feliz/ReactBindings/UseSyncExternalStore.test.fs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ type Components =
3535
snapshot
3636
)
3737

38-
3938
Html.div [
4039

4140
]

0 commit comments

Comments
 (0)