Closed pojntfx closed 4 years ago
Look like you are setting back the value by the previous content
c.newMessageContent = e.Get("target").Get("value").String()
@maxence-charriere Yeah sure, in the OnInput
handler; in the OnChange
handler (c.HandleMessageSend) however I'm clearing the value (when I "send a message"). No matter what I do, even if I call c.HandleMessageSend from the button, the text field isn't being cleared 🤷
Just in case you're still looking into this; I've found a solution after a few hours of troubleshooting. I'll post it here; the gist of it is that there is a difference between the HTML value
(for a text input) and the DOM value
- noticed this when working with checkboxes. Using component-local state I currently create the equivalent of a React ref
and update the DOM attributes manually on every render ;)
Code repo with the implementation: https://github.com/pojntfx/liwasc-frontend-web
The implementation (syncing the checked
DOM property with the go-app
state):
package components
import (
"github.com/maxence-charriere/go-app/v7/pkg/app"
)
type OnOffSwitchComponent struct {
app.Compo
On bool
OnToggle func(ctx app.Context, e app.Event)
ref app.HTMLInput
}
func (c *OnOffSwitchComponent) Render() app.UI {
c.Sync()
return c.ref
}
func (c *OnOffSwitchComponent) OnMount(ctx app.Context) {
c.Sync()
}
func (c *OnOffSwitchComponent) Sync() {
if c.ref == nil {
c.ref = app.Input().Type("checkbox").Checked(c.On).OnChange(c.OnToggle)
} else {
c.ref.JSValue().Set("checked", c.On)
}
}
And a small example of the DOM Sync (unidirectional dataflow; there is no two-way bindings magic here) as a video:
@maxence-charriere If you don't mind I'd add this to the documentation wiki ;)
Just one more small issue: With this approach, I can't pass components as "props" anymore because they won't be updated. I tried to implement the behaviour like so:
package components
import (
"fmt"
"log"
"github.com/maxence-charriere/go-app/v7/pkg/app"
)
type ExpandableSectionComponent struct {
app.Compo
Open bool
OnToggle func(ctx app.Context, e app.Event)
Title string
Content app.UI
}
func (c *ExpandableSectionComponent) Render() app.UI {
ref := app.Div().Class("pf-c-expandable-section__content").Hidden(!c.Open).Body(
c.Content,
)
app.Dispatch(func() {
if ref.JSValue() != nil {
log.Println("Setting hidden")
ref.JSValue().Set("hidden", !c.Open)
}
})
return app.Div().Class(fmt.Sprintf("pf-c-expandable-section pf-u-mb-md %v", func() string {
if c.Open {
return "pf-m-expanded"
}
return ""
}())).Body(
app.Button().Class("pf-c-expandable-section__toggle").Body(
app.Span().Class("pf-c-expandable-section__toggle-icon").Body(
app.I().Class("fas fa-angle-right"),
),
app.Span().Class("pf-c-expandable-section__toggle-text").Body(
app.Text(c.Title),
),
).OnClick(c.OnToggle),
ref,
)
}
I would expect the code in app.Dispatch
to be called after the render, and thus I'd expect ref.JSValue
to be !nil - however it is. Is there some way I could access the JSValue
of an element that I'm rendering in the Render
function? I need to access the JSValue
of a child component. I'd use the OnUpdate
callback but that seems to have been removed in v7 ...
Alright, one night later I actually got this to work. Even forked the repo to add a OnPostRender
callback, only to find out that it's actually possible in an elegant way with the current implementation ;) The issue with my code above is that ref
, even once I tried to access it's JSValue
in my OnPostRender
callback, might not be rendered - it's a child component after all, so that makes sense; this led to the following solution:
package components
import (
"fmt"
"github.com/maxence-charriere/go-app/v7/pkg/app"
)
type ExpandableSectionComponent struct {
app.Compo
Open bool
OnToggle func(ctx app.Context, e app.Event)
Title string
Content app.UI
}
func (c *ExpandableSectionComponent) Render() app.UI {
return app.Div().Class(fmt.Sprintf("pf-c-expandable-section pf-u-mb-md %v", func() string {
if c.Open {
return "pf-m-expanded"
}
return ""
}())).Body(
app.Button().Class("pf-c-expandable-section__toggle").Body(
app.Span().Class("pf-c-expandable-section__toggle-icon").Body(
app.I().Class("fas fa-angle-right"),
),
app.Span().Class("pf-c-expandable-section__toggle-text").Body(
app.Text(c.Title),
),
).OnClick(c.OnToggle),
&ExpandableSectionComponentContent{Content: c.Content, Open: c.Open},
)
}
type ExpandableSectionComponentContent struct {
app.Compo
Content app.UI
Open bool
}
func (c *ExpandableSectionComponentContent) Render() app.UI {
app.Dispatch(func() {
c.JSValue().Set("hidden", !c.Open)
})
return app.Div().Class("pf-c-expandable-section__content").Hidden(!c.Open).Body(
c.Content,
)
}
Pretty simple actually. app.Dispatch
is actually called right after the component of the Render
func - not the child component, so I simply created a nested component with the div which's JSValue
I want to access as the root component and access it in with the standard JSValue
func of app.Compo
. Now, using this approach, it is possible to modify JS attributes and take child components without anything going out of sync ;)
@maxence-charriere Would it be possible to add this to the Wiki, in an article like "Syncing DOM properties"? The wiki isn't editable directly but I could write the article and send it to you. Or maybe even adopt the defaultValue
and defaultChecked
prop conventions of React (which change the HTML attributes value
and checked
) and use the Value
and Checked
functions to edit the DOM properties instead? Using the latter, go-app
would be a bit more intuitive; however I might be biased as I work with React on the daily ;) I could create a PR if you'd like me to.
Inm gonna takena look to make the wiki editable.
Hi!
First of all, thanks for this library! I've had a lot of fun using it so far and I plan to use it for some serious frontends in the future. Sadly, I can't seem to be able to set the
value
of a HTML<input>
element, even when callingUpdate()
afterwards:I'd expect to the be able to clear the input in the
OnChange
handler, but nothing happens.newMessageContent
gets set, but the input still shows that last value. Any ideas?