Scene nodes initialize to null in script

Godot Version

4.6.3.stable

Question

Hi! I need to create custom nodes that have a button-like behavior but containing not just simple text. Right now my method is to create a scene with PanelContainer as root and override its panel stylebox to empty, than nest needed nodes. After that I nest a Button as the first child of the panel and adjust others’ mouse behavior to be passable to the button. The other way would be to script the creation of optionally internal Button node placed the same and connect to its signals instead.

This is feels much better than overriding _gui_input but still feels a little weird.

But now I have a weird error that wasn’t before: when I’m instantiating this scene and trying to assign new values to it, its nodes are initialized with null. Specifically:

# The node
extends PanelContainer

@onready var _name_l: Label = $Padding/Columns/Text/NameLabel

var name_: String:
	set(new):
		name_ = new
		_name_l.text = name_ # This fires error as _name_l is Nil

# The instance
	for d in item_data:
		var item = item_scene.instantiate()
		#var item = preload("res://ui/common/job_list_item.tscn").instantiate() # Doesn't work either
		item.name_ = d.name # This fires error
		items.add_child(item)

Scene tree:

What’s weird is that it wasn’t happening before, most scenes’ scripts are described similarly and don’t have a problem. I’ve tried restarting and recreating the scene but the issue persists. If I detach the script, I’ll get: “Invalid assignment of property or key ‘name_’ with value of type ‘String’ on a base object of type ‘PanelContainer’.” So it’s indeed instantiated.

Why this might happen?

Additionally, what would be the better way of creating such Control types that are spawned by script?

Thank you

@onready vars get initialized when the node enters the scene tree. Before that, they are null.

Because name_'s setter function is accessing the @onready var _name_l, you can’t change name_ before this node got added to the scene tree.

Depending on the setup, the engine may call your setters during node initialization process before the node is ready or even inserted into the scene tree. So if there’s some code there that depends on node’s ready status, you should guard it by checking is_node_ready()

Change it to:

var name_: String:
	set(new):
		name_ = new
		if not is_node_ready():
            await ready
		_name_l.text = name_

Just swap those two lines.

This is so stupid, especially considering I already had something like this and just forgot about it… Thank you very much!