diff --git a/src/django_components/component.py b/src/django_components/component.py
index 6e68bb81..c9de848b 100644
--- a/src/django_components/component.py
+++ b/src/django_components/component.py
@@ -228,6 +228,20 @@ class Component(View, metaclass=SimplifiedInterfaceMediaDefiningClass):
self.component_id = component_id or gen_id()
self._context: Optional[Context] = None
+ # When user first instantiates the component class before calling
+ # `render` or `render_to_response`, then we want to allow the render
+ # function to make use of the instantiated object.
+ #
+ # So while `MyComp.render()` creates a new instance of MyComp internally,
+ # if we do `MyComp(registered_name="abc").render()`, then we use the
+ # already-instantiated object.
+ #
+ # To achieve that, we want to re-assign the class methods as instance methods.
+ # For that we have to "unwrap" the class methods via __func__.
+ # See https://stackoverflow.com/a/76706399/9788634
+ self.render_to_response = types.MethodType(self.__class__.render_to_response.__func__, self) # type: ignore
+ self.render = types.MethodType(self.__class__.render.__func__, self) # type: ignore
+
def __init_subclass__(cls, **kwargs: Any) -> None:
cls._class_hash = hash(inspect.getfile(cls) + cls.__name__)
@@ -440,7 +454,13 @@ class Component(View, metaclass=SimplifiedInterfaceMediaDefiningClass):
)
```
"""
- comp = cls()
+ # This method may be called as class method or as instance method.
+ # If called as class method, create a new instance.
+ if isinstance(cls, Component):
+ comp: Component = cls
+ else:
+ comp = cls()
+
return comp._render(context, args, kwargs, slots, escape_slots_content)
def _render(
diff --git a/tests/test_component.py b/tests/test_component.py
index a92ff656..be4429f3 100644
--- a/tests/test_component.py
+++ b/tests/test_component.py
@@ -477,3 +477,35 @@ class ComponentRenderTest(BaseTestCase):
""",
)
+
+ @parametrize_context_behavior(["django", "isolated"])
+ def test_render_can_access_instance(self):
+ class TestComponent(component.Component):
+ template = "Variable: {{ id }}"
+
+ def get_context_data(self, **attrs):
+ return {
+ "id": self.component_id,
+ }
+
+ rendered = TestComponent(component_id="123").render()
+ self.assertHTMLEqual(
+ rendered,
+ "Variable: 123",
+ )
+
+ @parametrize_context_behavior(["django", "isolated"])
+ def test_render_to_response_can_access_instance(self):
+ class TestComponent(component.Component):
+ template = "Variable: {{ id }}"
+
+ def get_context_data(self, **attrs):
+ return {
+ "id": self.component_id,
+ }
+
+ rendered_resp = TestComponent(component_id="123").render_to_response()
+ self.assertHTMLEqual(
+ rendered_resp.content.decode('utf-8'),
+ "Variable: 123",
+ )