-
-
Notifications
You must be signed in to change notification settings - Fork 280
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add templ.Once function #750
Conversation
Is the decision to re-use the Have you had any thought about the use of // Once is a component that renders its children once per context.
func Once() OnceComponent {
_, fname, ln, _ := runtime.Caller(1)
return OnceComponent{
ID: fname + fmt.Sprint(ln),
}
} It could be cleaner, but I have a feeling it might not actually work with |
Yes, the idea was to reuse the existing map allocation, rather than introduce a new one. On the For example, you might have a On the other hand, I don't like that it's possible for someone to pollute the global "once" cache with something that's under their control. I wonder if the API design should default to runtime caller, but allow the cache to be overriden. I did think about using a type. Something like this: type onceObjectForMyPackage int
templ component() {
templ.Once[onceObjectForMyPackage](onceObjectForMyPackage(0)) {
<div>Hello</div>
}
} I thought that it could allow the caller to use a private type, therefore ensuring that no-one else could use the ID and block a script from running. |
Co-authored-by: Adrian Hesketh <adrianhesketh@hushmail.com>
…templ script as legacy in docs (#745) Co-authored-by: Joe Davidson <joe.davidson.21111@gmail.com>
In this case I would probably recommend something like: package deps
templ JQuery() {
templ.Once() {
<script />
}
} package button
templ Button() {
@deps.JQuery()
<button>Press the button</button>
} package link
templ Button() {
@deps.JQuery()
<a>Click the link</a>
} |
Oh, that's a nice design! I like that. |
I've updated the PR to demonstrate the use of private types so that clashes can be avoided. I worry that |
Definitely not dead against it, I think the key may be the safest and more robust way as long as users don't accidentally clash the key. I do think that no params is more ergonomic, but maybe the cost of that is things being expected. I tried to think about how we could make it more like sync.Once but came up with nothing.... this kind of thing: type once templ.Once
templ component() {
@once.Do() {
<div>Hello</div>
}
} I'm not sure the implementation would be any better than no params though. |
I think you're onto something. Defining a variable to lock something is the right way to think about it. The variable can also be exported from a package, to have a multi-package lock. In the latest commit, I've made it so that you can hard code the ID yourself if you want, but if you don't, it's just a random string. package once
var helloRenderLock = templ.MustNewRenderLock()
templ hello(label, name string) {
@helloRenderLock.Once() {
<script type="text/javascript">
function hello(name) {
alert('Hello, ' + name + '!');
}
</script>
}
<input type="button" value={ label } data-name={ name } onclick="hello(this.getAttribute('data-name'))"/>
}
templ render() {
@hello("Hello User", "user")
@hello("Hello World", "world")
} I haven't updated the docs, but what do you think of that design? |
That seems much more ergonomic! I'm on the fence about "lock" but I can't think of a better name hah. One final implementation to consider before locking in is pointers rather than IDs. The upside of this is the constructor won't error. But, it removes the option to provide a string id. // NewRenderLock proivdes a component that renders its children once per context.
func NewRenderLock() (once *RenderLock) {
once = &RenderLock{}
return once, nil
}
type RenderLock struct{}
func (o *RenderLock) Once() Component {
return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
_, v := getContext(ctx)
if v.getHasOnceBeenRendered(o) {
return nil
}
v.setHasOnceBeenRendered(o)
return GetChildren(ctx).Render(ctx, w)
})
} func (v *contextValue) setHasOnceBeenRendered(lock *RenderLock) {
if v.locks == nil {
v.locks = map[*RenderLock]struct{}{}
}
v.locks[lock] = struct{}{}
}
func (v *contextValue) getHasOnceBeenRendered(lock *RenderLock) (ok bool) {
if v.locks == nil {
v.locks = map[*RenderLock]struct{}{}
}
_, ok = v.locks[lock]
return
} |
I'm glad we thrashed this out. I think this is the one. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great, I think the API is really clean!
That's a sneaky gotcha, I knew this was the case for things like pointers to primitive values but not structs!
// | Two distinct zero-size variables may
// | have the same address in memory
No description provided.