{"record":{"id":"622ec02f1c433eb0","repo":"Textualize/textual","slug":"node-id-attribute-may-not-be-changed-once-set-c","errorCode":null,"errorMessage":"Node 'id' attribute may not be changed once set (current id={self._id!r})","messagePattern":"Node 'id' attribute may not be changed once set \\(current id=(.+?)\\)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/textual/dom.py","lineNumber":827,"sourceCode":"    @property\n    def id(self) -> str | None:\n        \"\"\"The ID of this node, or None if the node has no ID.\"\"\"\n        return self._id\n\n    @id.setter\n    def id(self, new_id: str) -> str:\n        \"\"\"Sets the ID (may only be done once).\n\n        Args:\n            new_id: ID for this node.\n\n        Raises:\n            ValueError: If the ID has already been set.\n        \"\"\"\n        check_identifiers(\"id\", new_id)\n        self._nodes.updated()\n        if self._id is not None:\n            raise ValueError(\n                f\"Node 'id' attribute may not be changed once set (current id={self._id!r})\"\n            )\n        self._id = new_id\n        return new_id\n\n    @property\n    def name(self) -> str | None:\n        \"\"\"The name of the node.\"\"\"\n        return self._name\n\n    @property\n    def css_identifier(self) -> str:\n        \"\"\"A CSS selector that identifies this DOM node.\"\"\"\n        tokens = [self.__class__.__name__]\n        if self.id is not None:\n            tokens.append(f\"#{self.id}\")\n        return \"\".join(tokens)\n","sourceCodeStart":809,"sourceCodeEnd":845,"githubUrl":"https://github.com/Textualize/textual/blob/06dbeef4bb70fb718236aa418ed658ef4667a126/src/textual/dom.py#L809-L845","documentation":"A DOMNode's id is immutable once set: the id setter raises ValueError if you try to assign a new id after one is already assigned. Textual relies on stable ids for querying and CSS matching.","triggerScenarios":"Setting widget.id = 'a' and later widget.id = 'b'; passing id= to the constructor and then assigning widget.id again; reusing a widget instance and trying to relabel it.","commonSituations":"Recycling widget instances in a list/table that gets new data; programmatically assigning ids in a loop where a variable collision reassigns; refactoring from constructor id to property assignment and doing both.","solutions":["Set the id exactly once — preferably via the constructor: Widget(id='name')","If you need a mutable label, use a custom attribute or the widget's content (e.g. label/update) instead of id","Create a fresh widget instance rather than re-id'ing an existing one when data changes"],"exampleFix":"# before\nwidget.id = 'header'\n# ...\nwidget.id = 'header2'  # ValueError\n\n# after\nwidget = Widget(id='header2')  # recreate or set only once at construction","handlingStrategy":"validation","validationCode":"def set_id_once(widget, new_id: str):\n    if widget.id is not None:\n        raise ValueError(f'id already set to {widget.id!r}')\n    widget.id = new_id","typeGuard":"def can_set_id(widget) -> bool:\n    return widget.id is None","tryCatchPattern":"try:\n    widget.id = 'x'\nexcept ValueError:\n    widget = widget.clone_with_id('x') if hasattr(widget, 'clone_with_id') else Widget(id='x')","preventionTips":["Set ids at construction only: Widget(id='name')","Never re-id existing instances; recreate instead","Use distinct attributes for mutable labels"],"tags":["textual","id","immutability","dom"],"backgroundTag":"immutable-id-reassignment","analyzedSha":"06dbeef4bb70fb718236aa418ed658ef4667a126","analyzedAt":"2026-08-27T02:36:57.214Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}