Skip to content
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

How to update a rendered element? #304

Closed
Naatan opened this issue Jul 3, 2022 · 10 comments
Closed

How to update a rendered element? #304

Naatan opened this issue Jul 3, 2022 · 10 comments

Comments

@Naatan
Copy link

Naatan commented Jul 3, 2022

Due to lack of documentation I'm working off of examples, issues and backwards engineering. Sadly I feel like the issue of updating already rendered elements isn't easily discerned through any of those channels.

From the example code I found that I probably want to use vecty.Rerender(elem). But so far any attempt I give this results in the following error:

panic: vecty: next child render must not equal previous child render (did the child Render illegally return a stored render variable?)

I find this error hard to understand. Is it telling me that once rendered an item cannot be rendered again? And if so, how am I meant to update rendered elements? I'm guessing there's a detail I'm missing.

The todo example doesn't seem to be doing anything fancy, I'm not sure what I'm doing wrong.

For what it's worth, here's an excerpt of the code I'm working with:

type listitem struct {
	vecty.Core
	selected      bool
}

func (c *listitem) onClick(e *vecty.Event) {
	c.selected = true
	vecty.Rerender(c)
}

func (c *listitem) Render() vecty.ComponentOrHTML {
	return elem.Div(
		vecty.Markup(
			vecty.MarkupIf(c.selected, vecty.Class("selected")),
			event.Click(c.onClick),
		),
	)
}
@emidoots
Copy link
Member

emidoots commented Jul 3, 2022

Make sure that your Render method is always responsible for creating new components/elements. Probably what you are doing is storing listitem in a list somewhere and returning that multiple times in your Render function. You need to return new listitems every time Render is called.

@Naatan
Copy link
Author

Naatan commented Jul 3, 2022

Thanks, that was also the direction I was trying to move in. I had changed my component to instead of receiving a slice of MarkupOrChild instead use a callback to receive that same slice. No dice so far though.. still trying to find out what I'm doing wrong.

What's the "vecty" way of creating a component that accepts child elements / components?

@Naatan
Copy link
Author

Naatan commented Jul 3, 2022

Turns out I missed a component. After changing my components to look like this:

type listitem struct {
	vecty.Core
	selected      bool
	markupOrChild func() []vecty.MarkupOrChild
}

It now works. But the markup is getting super unwieldy. Is this the recommended way? It does not feel intuitive.. sadly the intuitive approach is evidently error prone.

@Naatan
Copy link
Author

Naatan commented Jul 3, 2022

Also have to wonder what this does for previously initialized structs. eg. if I set c.selected=true and it gets rerendered then either the property change is lost or vecty is doing something really questionable. Appreciate any insights into how the process works.

@VinceJnz
Copy link

VinceJnz commented Jul 4, 2022

I struggled with this when I was trying to get it to work.

Have you looked at the todomvc example.

func (p *PageView) renderItemList() *vecty.HTML {
var items vecty.List
for i, item := range store.Items {
if (store.Filter == model.Active && item.Completed) || (store.Filter == model.Completed && !item.Completed) {
continue
}
items = append(items, &ItemView{Index: i, Item: item})
}

This renders a list of store.Items
Each time through the for loop it retrieves an item from store.Items, it populates a new ItemView with the item data and appends it to items (vecty.List).

properties vs. state
Structure fields can be either a property or a state
If they are to be a property then you need to add `vecty:"prop"`
e.g.

type ItemView struct {
	vecty.Core
	SomePropField `vecty:"prop"`
	SomeStateField
}

When an ItemView is rerendered:

  • Prop fields are replaced with new values that are provided by the parent when the ItemView is created.
  • State fields and their values are kept.

@Naatan
Copy link
Author

Naatan commented Jul 4, 2022

I see. So basically Vecty wants you to completely separate the rendering code from the state management code? I guess that makes sense, and is likely somewhere where most devs would end up anyway after the prototyping phase. It does still feel quite error prone though.

Appreciate the insights! I'm at least unblocked for now. For what it's worth I'd consider this the only part of vecty so far that I'd like to see some improvement on, everything else has been great! And to be fair it's not that this part is "bad", it's just not intuitive and prone to error.

@Naatan
Copy link
Author

Naatan commented Jul 4, 2022

By the way, can anyone tell me what this panic achieves? It seems disabling the panic makes the code function exactly the way I had assumed it would. Though I'm guessing it will create some type of breakage in other areas that I haven't reached?

@VinceJnz
Copy link

VinceJnz commented Jul 4, 2022

It might be worth having a read of the following discussion

#291

@Naatan
Copy link
Author

Naatan commented Jul 4, 2022

@VinceJnz Thanks, that's a useful read. Is implied in your response that the mechanic that this panic guards for is specifically this prop vs state mechanic?

It feels like a steep price to pay, surely there's better ways of addressing this use-case.

@Naatan
Copy link
Author

Naatan commented Jul 5, 2022

fwiw I ended up working around this issue with the following helper:


type MarkupOrChildProxy struct {
	vecty.Core
	Child vecty.MarkupOrChild
}

func RenderMarkupOrChild(markupOrChild ...vecty.MarkupOrChild) []vecty.MarkupOrChild {
	result := []vecty.MarkupOrChild{}
	for _, m := range markupOrChild {
		switch v := m.(type) {
		case vecty.MarkupList:
			result = append(result, v)
		case vecty.Component, *vecty.HTML, vecty.List, vecty.KeyedList:
			result = append(result, &MarkupOrChildProxy{Child: v})
		default:
			panic(fmt.Sprintf("unsupported markupOrChild type: %T", v))
		}
	}
	return result
}

func (c *MarkupOrChildProxy) Render() vecty.ComponentOrHTML {
	return c.Child.(vecty.ComponentOrHTML)
}

func (c *MarkupOrChildProxy) SkipRender(prev vecty.Component) bool {
	switch prev.(type) {
	case *MarkupOrChildProxy:
		return true
	}
	return false
}

This was influenced by other vecty projects I found that were using similar helpers.

Hopefully this type of workaround won't be required once vecty goes stable.

@Naatan Naatan closed this as completed Jul 5, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants