Open soypat opened 2 years ago
These are the notes that I have found and added to on state vs. property
Related issue: https://github.com/gopherjs/vecty/issues/209
What does the 'vecty:"prop"'
struct tag do?
Any variable tagged with 'vecty:"prop"'
will have its value over-written when it is updated by the parent. Other variables are called state values.
The way this is implemented in vecty: The old copy of the struct is kept and the prop variables are over-written with the prop variables from the new struct. The new struct is discarded. So the old copy becomes the new master version with the prop variables over written, and the state variables are kept unchanged.
This allows:
If the value of a variable is set by the parent and is not changed within the methods of the current struct then is should be set as a prop variable (i.e. is will have the 'vecty:"prop"'
tag)
If the value of a variable is set in a function in the struct then it should be set as a state variable (i.e. no tag)
These rules apply to all type of variables and include types: struct, func, pointer, interface, etc.
To be able to use a pointer to the correct version of state variables you will need to make the pointer from the first instance of the structure. This applies to func, pointers, interfaces. This is because the old (original) structure is the master for all state variables. This is not recommended.
This has a big impact when you are rendering elements: e.g. a new child is created when rendering the parent. The original child is kept along with state variables and prop variable are over written.
Getting this wrong can cause errors like: panic: vecty: Rerender invoked on Component that has never been rendered
because you are trying to render the new version of the struct.
More notes on panic: vecty: Rerender invoked on Component that has never been rendered
It is important to seperate the store/data fetching from the view rendering.
For example do not include the data fetch functions into the view. Have the data read done outside the view and then call the view rerender.
I think that can be confusing because you are then applying semantic meaning to public vs. private as being "public to the component" and "private to the component" rather than to the package. Effectively, you would be replacing 'vecty:"prop"'
with whether or not a field is public or not. This could be very confusing when working in e.g. one Go package
this said another way, if you made a counter field public on a component, its value would no longer persist across renders which would be mighty confusing I think.
What matters to me the most is the mental model of the Vecty API. Coming from a React background, I just always expect the parent to be the ruler of the most updated values to a given prop, regardless of how many times it rerenders. If a child wants to keep its own state, then it just copies the value to another field during Mount time. I agree about the private/public comment you made.
This gets asked a lot, what @VinceJnz said seems all accurate though, but actually there's a very simple answer.
vecty:"prop"
vecty:"prop"
Pretend you have a AlarmClock
component, and you have a goroutine which calls vecty.Rerender(alarmClock)
every second to update it. Let's say the Render
method of AlarmClock
returns some HTML like The current time is:
and then renders a child component &CurrentTime{Time: alarmClock.time}
If the CurrentTime.Time
field is tagged as vecty:"prop"
then every time the parent AlarmClock
is rerendered, the value it passes into &CurrentTime{Time: alarmClock.time}
is going to end up being used, e.g. it'll update like you would expect an alarm clock to!
But if it's not tagged with vecty:"prop"
? Then the first time the parent renders &CurrentTime{Time: alarmClock.time}
the Time
field gets set to e.g. a value of 1
, but even when it gets rerendered with a new value 2
the CurrentTime
component will actually just continue using the original (first-render) value of 1
! The alarm clock doesn't update!
vecty:"prop"
Let's say you have a GameWorld
component which renders &Player{Health: 100}
so the player starts out with 100 health points. In response to input, you call a method on your Player
component player.takeDamage()
which sets player.Health -= 1
and then calls vecty.Rerender(player)
so it shows the new health value.
If Health
is tagged with vecty:"prop"
then you'll see some weird behavior here! Any time vecty.Rerender(gameWorld)
is used - say to add more monsters to your game world - then suddenly your player's health gets reset back to 100
! What gives?! It's because Health
being a vecty:"prop"
is saying that the parent chooses the value of the field, and so if our parent gets rerendered.. it's choosing the value!
A vecty:"prop"
has to be a public field, state can be public or private fields (but is advised to be private.)
This is simply because Go reflection cannot access private fields, which is how vecty:"prop"
is implemented behind the scenes.
Thanks for the explanation @slimsag! I just noticed I was doing some hacky workarounds to get things that should have been props to render, like calling a struct's render method instead of embedding it!
All in all I think I've understood when to use one or the other. Here's an example for whomever wants to play around with slimsags Alarm Clock idea, it really helped drive the idea home. Click on the time to cause a rerender on the parent's (AlarmClock) side.
I've noticed component structs sometimes have fields with
vecty:"prop"
tag. I've also come across the issue https://github.com/hexops/vecty/issues/209 and the generics issue https://github.com/hexops/vecty/issues/277.What is this separation?Why is it necessary? I've built vecty apps without it just fine.