diff --git a/CHANGELOG.md b/CHANGELOG.md
index 11bddf3..b1dd472 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,3 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+
+### Added
+
+- `useConstructor` hook that executes a provided callback only once during the component's lifecycle, similar to a constructor.
diff --git a/cypress/component/useConstructor.cy.tsx b/cypress/component/useConstructor.cy.tsx
new file mode 100644
index 0000000..429618f
--- /dev/null
+++ b/cypress/component/useConstructor.cy.tsx
@@ -0,0 +1,65 @@
+import React, { useState } from "react";
+import { useConstructor } from "../../src/lib/hooks/useConstructor";
+
+describe("useConstructor", () => {
+ it("calls the callback exactly once per mount, even after re-renders", () => {
+ const initSpy = cy.spy().as("initSpy");
+
+ const TestComponent: React.FC = () => {
+ const [count, setCount] = useState(0);
+ useConstructor(() => {
+ initSpy();
+ });
+
+ return (
+
+ {count}
+
+
+ );
+ };
+
+ // Turn off StrictMode as it will double‑invoke mount logic
+ cy.mount(, { strict: false });
+
+ cy.get("@initSpy").should("have.been.calledOnce");
+
+ // Cause several re-renders
+ cy.contains("Increment").click().click().click();
+ cy.get("[data-cy='count']").should("have.text", "3");
+
+ cy.get("@initSpy").should("have.been.calledOnce");
+ });
+
+ it("runs again after an unmount/remount (new instance)", () => {
+ const initSpy = cy.spy().as("initSpy");
+
+ const Wrapper: React.FC = () => {
+ const [show, setShow] = useState(true);
+ return (
+