diff --git a/lab/functional-lit/stateful.ts b/lab/functional-lit/stateful.ts
index cc7f2e270c04bd3f66e846ca535fdd92f46b971d..bc936fdd81ae328955a095478f8ea8214ca30c08 100644
--- a/lab/functional-lit/stateful.ts
+++ b/lab/functional-lit/stateful.ts
@@ -4,16 +4,21 @@ import { directive, PartType } from 'lit/directive.js';
 import { type Observable } from 'rxjs';
 import type { Renderable } from './renderable.js';
 
+interface Hook {
+  cleanup?: Function | void;
+  value?: any;
+  deps?: any;
+}
+
 // no memory leak due to weakmap
-const componentHooks: WeakMap<StatefulDirective, { cleanup?: Function | void; value?: any; deps?: any }[]> =
-  new WeakMap();
+const componentHooks: Record<number, Hook[]> = {};
 let currentComponent: StatefulDirective | null = null;
 let hookIndex = 0;
 let isRendering = false;
 
-function setCurrentComponent(component: StatefulDirective, resumeFromIndex = 0) {
+function setCurrentComponent(component: StatefulDirective, id: number) {
   currentComponent = component;
-  hookIndex = resumeFromIndex;
+  hookIndex = 0;
 }
 
 function getCurrentComponent() {
@@ -28,11 +33,11 @@ function useState<T>(initialState: T): [T, (newState: T) => void] {
   const currentHookIndex = hookIndex++;
 
   // Initialize hooks array for component if not exists
-  if (!componentHooks.has(component)) {
-    componentHooks.set(component, []);
+  if (!componentHooks[component.id]) {
+    componentHooks[component.id] = [];
   }
 
-  const hooks = componentHooks.get(component)!;
+  const hooks = componentHooks[component.id]!;
 
   // If hook doesn't exist, initialize it
   if (currentHookIndex >= hooks.length) {
@@ -47,12 +52,7 @@ function useState<T>(initialState: T): [T, (newState: T) => void] {
           'Try to move the state update to a place where it is not called during a render like a lifecycle method or a event handler.',
       );
     hooks[currentHookIndex] = { value: newState };
-    // Trigger re-render (in Lit, this would be a property change)
-    if (component && typeof component.hookUpdated === 'function') {
-      (component as StatefulDirective).hookUpdated();
-    } else {
-      throw new Error("We're not in a hookable scope");
-    }
+    component.hookUpdated();
   };
 
   return [hooks[currentHookIndex]?.value, setState];
@@ -64,11 +64,11 @@ function useEffect(useEffectCallback: () => void | Teardown, deps?: any[]) {
   const currentHookIndex = hookIndex++;
 
   // Initialize hooks array for component if not exists
-  if (!componentHooks.has(component)) {
-    componentHooks.set(component, []);
+  if (!componentHooks[component.id]) {
+    componentHooks[component.id] = [];
   }
 
-  const hooks = componentHooks.get(component)!;
+  const hooks = componentHooks[component.id]!;
 
   // If no previous deps or deps have changed
   const hasDepsChanged =
@@ -95,33 +95,42 @@ function useEffect(useEffectCallback: () => void | Teardown, deps?: any[]) {
   }
 }
 
-function useMemo<T>(computeValue: () => [T, () => any] | [T] | void, deps?: any[]): T | undefined {
+function _useMemo<T>(computeValue: (hook: Hook) => [T, () => any] | [T] | void, deps?: any[]): Hook {
   const currentComponent = getCurrentComponent();
   const currentHookIndex = hookIndex++;
 
   // Ensure hooks array exists for current component
-  if (!componentHooks.has(currentComponent)) {
-    componentHooks.set(currentComponent, []);
+  if (!componentHooks[currentComponent.id]) {
+    componentHooks[currentComponent.id] = [];
   }
 
-  const hooks = componentHooks.get(currentComponent)!;
+  const hooks = componentHooks[currentComponent.id];
+  if (!hooks) throw new Error('hooks of current component not found, should not happen');
 
   // Check if hook already exists and deps haven't changed
   if (deps && hooks[currentHookIndex]?.deps?.every((dep: any, index: number) => dep === deps[index])) {
-    return hooks[currentHookIndex].value;
+    return hooks[currentHookIndex];
   }
 
-  // Compute new value
-  const [newValue, cleanup] = computeValue() ?? [];
+  // cleanup old hook and create new
+  hooks[currentHookIndex]?.cleanup?.();
 
-  // Store the computed value and dependencies
-  hooks[currentHookIndex] = {
-    value: newValue,
+  const hook: Hook = {
     deps,
-    cleanup,
   };
+  const [newValue, cleanup] = computeValue(hook) ?? [];
+  hook.value = newValue;
+  hook.cleanup = cleanup;
+
+  hooks[currentHookIndex] = hook;
+
+  return hooks[currentHookIndex];
+}
 
-  return newValue;
+function useMemo<T>(computeValue: () => [T, () => any] | [T], deps?: any[]): T;
+function useMemo(computeValue: () => void, deps?: any[]): void;
+function useMemo<T>(computeValue: () => [T, () => any] | [T] | void, deps?: any[]): T | undefined {
+  return _useMemo(computeValue, deps).value;
 }
 
 /**
@@ -130,43 +139,31 @@ function useMemo<T>(computeValue: () => [T, () => any] | [T] | void, deps?: any[
  * the function will be called in an infinite loop, which is definitely not what you want.
  */
 function useObservable<T>(observable: () => Observable<T>, deps: any[]): T | undefined {
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
   if (!deps) throw new Error('useObservable requires deps');
 
-  /** 
- * we need the setValueInternal becuase
- * - synchronous initial values
- * - no endless loops (we cannot call setValue synchronously)
- * - make sure value is always set synchronously (if synchronous initial value) even with other hooks inside the closure
- * 
- * ```
- *  stateful(() => {
-      const selectedLang = useObservable(() => globalLanguageSubject.asObservable(), []);
-      const design = useObservable(() => DesignSystem.currentTheme, []);
-
-      console.log("selected Lang: ", selectedLang, design); // -> needs language and design set on every log
-    })
- * ```
- * */
-
-  let setValueInternal: (newState: T | undefined) => void;
-  let valueInternal: T | undefined;
-
-  useMemo(() => {
+  return _useMemo(hook => {
+    const component = getCurrentComponent();
+    let reqFrameReference: number;
     const subscription = observable().subscribe(value => {
-      valueInternal = value;
-      return requestAnimationFrame(() => setValueInternal(value));
+      console.log('subscription fired', deps);
+      hook.value = value;
+      reqFrameReference = requestAnimationFrame(() => component.hookUpdated());
     });
-    return [valueInternal, () => subscription.unsubscribe()];
-  }, deps);
-
-  const [value, setValue] = useState<T | undefined>(valueInternal ?? undefined);
-
-  valueInternal = value;
-  setValueInternal = setValue;
-
-  return value;
+    return [
+      hook.value,
+      () => {
+        cancelAnimationFrame(reqFrameReference);
+        subscription.unsubscribe();
+      },
+    ];
+  }, deps).value;
 }
 
+// * this represents the part, not the stateful component
+// notably, if you render another stateful directive into the same part
+// it will not call the constructor and `this` is object identical between both.
+// Therefore we use an additional ID instead of relying on a WeakMap of `this`
 class StatefulDirective extends AsyncDirective {
   constructor(partInfo: PartInfo) {
     super(partInfo);
@@ -177,7 +174,9 @@ class StatefulDirective extends AsyncDirective {
 
   renderFn!: Function;
   props?: any;
-  render(value: Function, props: any) {
+  id!: number;
+  render(value: Function, props: any, id: number) {
+    this.id = id;
     this.renderFn = value;
     this.props = props;
 
@@ -195,7 +194,7 @@ class StatefulDirective extends AsyncDirective {
 
     // signal updates
     this.dispose = effect(() => {
-      setCurrentComponent(this);
+      setCurrentComponent(this, this.id);
       isRendering = true;
       result = this.renderFn(this.props);
       if (updateFromDirective) {
@@ -211,12 +210,12 @@ class StatefulDirective extends AsyncDirective {
     return result;
   }
 
-  override update(_part: ChildPart, props: [Function, any]): unknown {
+  override update(_part: ChildPart, props: [Function, any, number]): unknown {
     // this exposes the current state of the properties to the parent element, therefore making it inspectable
     // @ts-expect-error this property doesn't exist and that's OK since it's just an outlet for debugging
     _part.parentNode.__STATE = props;
 
-    return this.render(props[0], props[1]);
+    return this.render(props[0], props[1], props[2]);
   }
 
   // hook updates
@@ -227,7 +226,8 @@ class StatefulDirective extends AsyncDirective {
   override disconnected(): void {
     super.disconnected();
 
-    componentHooks.get(this)?.forEach(hook => hook.cleanup?.());
+    componentHooks[this.id]?.forEach(hook => hook.cleanup?.());
+    delete componentHooks[this.id];
     this.dispose?.();
   }
 
@@ -239,9 +239,13 @@ class StatefulDirective extends AsyncDirective {
 }
 const statefulDirective = directive(StatefulDirective);
 
-export const stateful =
-  <P extends void | { [key: string]: any }>(fn: (props: P) => Renderable) =>
-  (props: P) =>
-    statefulDirective(fn, props);
+// * theoretically, we could have an issue after Number.MAX_SAFE_INTEGER stateful directives. That's unrealistic though.
+let idCounter = 0;
+export const stateful = <P extends void | { [key: string]: any }>(fn: (props: P) => Renderable) => {
+  const id = idCounter++;
+  return (props: P) => {
+    return statefulDirective(fn, props, id);
+  };
+};
 
 export { getCurrentComponent, setCurrentComponent, useEffect, useMemo, useObservable, useState };