Closed leolorenzoluis closed 2 years ago
Hmm, this message should be displayed when hooks are not called consistently. Do you have a conditional or similar in the function that wraps the Hook.useState/useRef
calls? Can I see the whole function? Is it decorated with HookComponent
/LitElement
or is it a helper function? Is it inlined? Thanks!
Yes I do. I have refactored it a little bit trying to pull from your TodoMVC 👯 with just scoped css but I get same error.
[<HookComponent>]
let Page() =
let model, dispatch = Hook.useElmish(init, update)
let renderItem (item: Monitor) =
match model.Edit with
| Some value when value.Id = item.Id->
html $"""
<div>
<p>{item.Id}</p>
<input value={item.FriendlyName} @keyup={EvVal (fun e -> UpdateFriendlyName e |> dispatch)}/>
<input value={item.URL}/>
<sl-button @click={Ev (fun e -> SaveMonitor model.Edit.Value |> dispatch)}>Save monitor</sl-button>
</div>
"""
| _ ->
let transitionMs = 500
let transition = Hook.useTransition(transitionMs, onLeft = (fun () -> DeleteMonitor item.Id |> dispatch))
let className = Hook.use_scoped_css $"""
:host {{
transition-duration: {transitionMs}ms;
border: 2px solid lightgray;
border-radius: 10px;
margin: 5px 0;
}}
:host.transition-enter {{
opacity: 0;
transform: scale(2);
}}
:host.transition-leave {{
opacity: 0;
transform: scale(0.1);
}}
.is-clickable {{
user-select: none;
}}
"""
html $"""
<div class="{className} {transition.className}">
<p>{item.Id} {item.FriendlyName} {item.URL}</p>
<sl-button @click={Ev (fun e -> DeleteMonitor item.Id |> dispatch)}>Delete monitor</sl-button>
<sl-button @click={Ev (fun e -> EditMonitor item |> dispatch)}>Edit monitor</sl-button>
</div>
"""
html $"""
<sl-button @click={Ev (fun e ->
let newMonitor = {
Id = Random.Shared.Next()
FriendlyName = "Test1"
URL = "2"
Retries = 100
HeartBeatInterval = 100
}
CreateMonitor newMonitor |> dispatch)}>New monitor</sl-button>
{model.Monitors |> Seq.map renderItem}
"""
Maybe I'm doing something I shouldn't like mixing useElmish with useState, etc...?
Just as with React, hooks should appear on top of the components and not be affected by the control flow. In your case renderItem
is being called for each item, so the following lines are called multiple times:
let transition = Hook.useTransition(transitionMs, onLeft = (fun () -> DeleteMonitor item.Id |> dispatch))
let className = Hook.use_scoped_css $"""
The solution would be to extract that code into a separate component (below is a sample, you would have to pass the dispatch function to make it compilable:
[<HookComponent>]
let Monitor(item: Monitor) =
let transitionMs = 500
let transition = Hook.useTransition(transitionMs, onLeft = (fun () -> DeleteMonitor item.Id |> dispatch))
let className = Hook.use_scoped_css $"""
:host {{
transition-duration: {transitionMs}ms;
border: 2px solid lightgray;
border-radius: 10px;
margin: 5px 0;
}}
:host.transition-enter {{
opacity: 0;
transform: scale(2);
}}
:host.transition-leave {{
opacity: 0;
transform: scale(0.1);
}}
.is-clickable {{
user-select: none;
}}
"""
html $"""
<div class="{className} {transition.className}">
<p>{item.Id} {item.FriendlyName} {item.URL}</p>
<sl-button @click={Ev (fun e -> DeleteMonitor item.Id |> dispatch)}>Delete monitor</sl-button>
<sl-button @click={Ev (fun e -> EditMonitor item |> dispatch)}>Edit monitor</sl-button>
</div>
"""
[<HookComponent>]
let Page() =
let model, dispatch = Hook.useElmish(init, update)
let renderItem (item: Monitor) =
match model.Edit with
| Some value when value.Id = item.Id->
html $"""
<div>
<p>{item.Id}</p>
<input value={item.FriendlyName} @keyup={EvVal (fun e -> UpdateFriendlyName e |> dispatch)}/>
<input value={item.URL}/>
<sl-button @click={Ev (fun e -> SaveMonitor model.Edit.Value |> dispatch)}>Save monitor</sl-button>
</div>
"""
| _ -> Monitor item
html $"""
<sl-button @click={Ev (fun e ->
let newMonitor = {
Id = Random.Shared.Next()
FriendlyName = "Test1"
URL = "2"
Retries = 100
HeartBeatInterval = 100
}
CreateMonitor newMonitor |> dispatch)}>New monitor</sl-button>
{model.Monitors |> Seq.map renderItem}
"""
Awesome. Thank you @alfonsogarciacaro!
Given the following code that is just rendering a list of items
Error: Hooks must be called consistently for each render call at HookContext.fail (Hook.fs:95:9) at HookContext.checkRendering (Hook.fs:126:32) at HookContext.useState (Hook.fs:171:9)