From fc7cbe3fc7671cf0fac58f30e3a159b376351e35 Mon Sep 17 00:00:00 2001 From: MarcelOlsen Date: Fri, 13 Feb 2026 23:18:32 +0100 Subject: [PATCH 1/5] feat: add fiber type definitions and update core types Add Fiber, FiberRoot, Hook, Effect types with WorkTag, Lane, and Flags branded types. Update core/types.ts to export hook-related types and remove old VDOMInstance. --- src/core/types.ts | 24 +- src/fiber/types.ts | 539 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 549 insertions(+), 14 deletions(-) create mode 100644 src/fiber/types.ts diff --git a/src/core/types.ts b/src/core/types.ts index c3b1c83..df5963a 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -91,21 +91,17 @@ export const TEXT_ELEMENT = "TEXT_ELEMENT"; export const FRAGMENT = Symbol("react.fragment"); export const PORTAL = Symbol("react.portal"); +import type { PortalElement } from "../portals/types"; + // ******************* // -// VDOM Instance Types // +// Public Hook Types // // ******************* // -export interface VDOMInstance { - element: JSXElementType; - dom: Node | null; - childInstances: VDOMInstance[]; - parent?: VDOMInstance; - hooks?: StateOrEffectHook[]; - hookCursor?: number; - contextValues?: Map; // For context providers - rootContainer?: HTMLElement; // Track root container for root-level instances -} +export type EffectCallback = (() => void) | (() => () => void); +export type DependencyList = readonly unknown[]; -// Import hook types from hooks module -import type { StateOrEffectHook } from "../hooks/types"; -import type { PortalElement } from "../portals/types"; +export type Reducer = (state: State, action: Action) => State; + +export type MutableRefObject = { + current: T; +}; diff --git a/src/fiber/types.ts b/src/fiber/types.ts new file mode 100644 index 0000000..df4cd83 --- /dev/null +++ b/src/fiber/types.ts @@ -0,0 +1,539 @@ +/* **************** */ +/* Fiber Type Definitions */ +/* **************** */ + +import type { AnyMiniReactElement, ElementType } from "../core/types"; + +// ============================================ +// WorkTag - Component type identifiers +// ============================================ + +/** + * Work tags identify the type of fiber node. + * Using const object + satisfies pattern instead of enum. + */ +export const WorkTag = { + FunctionComponent: 0, + ClassComponent: 1, // Reserved for future use + IndeterminateComponent: 2, // Before we know if it's function or class + HostRoot: 3, // Root of a host tree (e.g., ReactDOM.render) + HostPortal: 4, // A subtree rendered into a different container + HostComponent: 5, // DOM elements like 'div', 'span' + HostText: 6, // Text nodes + Fragment: 7, // React.Fragment + Mode: 8, // Reserved for StrictMode, ConcurrentMode + ContextConsumer: 9, + ContextProvider: 10, + ForwardRef: 11, // Reserved for future use + Profiler: 12, // Reserved for future use + SuspenseComponent: 13, // Reserved for future use + MemoComponent: 14, // React.memo wrapped components + SimpleMemoComponent: 15, // Simplified memo (no custom compare) + LazyComponent: 16, // React.lazy components + OffscreenComponent: 17, // Reserved for offscreen rendering +} as const satisfies Record; + +export type WorkTag = (typeof WorkTag)[keyof typeof WorkTag]; + +// ============================================ +// Branded Types - Type-safe numeric identifiers +// ============================================ + +/** + * Lane represents a single priority level. + * Branded type prevents mixing with regular numbers. + */ +declare const LaneBrand: unique symbol; +export type Lane = number & { readonly [LaneBrand]: typeof LaneBrand }; + +/** + * Lanes represents a bitmask of multiple lanes. + * Can contain multiple Lane values OR'd together. + */ +declare const LanesBrand: unique symbol; +export type Lanes = number & { readonly [LanesBrand]: typeof LanesBrand }; + +/** + * Flags represent fiber state flags (effects, placement, etc). + * Branded type for type safety. + */ +declare const FlagsBrand: unique symbol; +export type Flags = number & { readonly [FlagsBrand]: typeof FlagsBrand }; + +// Factory functions for creating branded types +export const createLane = (value: number): Lane => value as Lane; +export const createLanes = (value: number): Lanes => value as Lanes; +export const createFlags = (value: number): Flags => value as Flags; + +// ============================================ +// Lane Constants - Priority levels +// ============================================ + +export const NoLane: Lane = createLane(0b0000000000000000000000000000000); +export const NoLanes: Lanes = createLanes(0b0000000000000000000000000000000); + +export const SyncLane: Lane = createLane(0b0000000000000000000000000000001); +export const InputContinuousLane: Lane = + createLane(0b0000000000000000000000000000100); +export const DefaultLane: Lane = createLane(0b0000000000000000000000000010000); +export const TransitionLane1: Lane = + createLane(0b0000000000000000000000001000000); +export const TransitionLane2: Lane = + createLane(0b0000000000000000000000010000000); +export const IdleLane: Lane = createLane(0b0100000000000000000000000000000); +export const OffscreenLane: Lane = + createLane(0b1000000000000000000000000000000); + +// ============================================ +// Effect Flags - Side effect markers +// ============================================ + +export const NoFlags: Flags = createFlags(0b0000000000000000000000000000); +export const PerformedWork: Flags = createFlags(0b0000000000000000000000000001); +export const Placement: Flags = createFlags(0b0000000000000000000000000010); +export const Update: Flags = createFlags(0b0000000000000000000000000100); +export const ChildDeletion: Flags = createFlags(0b0000000000000000000000010000); +export const ContentReset: Flags = createFlags(0b0000000000000000000000100000); +export const Callback: Flags = createFlags(0b0000000000000000000001000000); +export const DidCapture: Flags = createFlags(0b0000000000000000000010000000); +export const ForceClientRender: Flags = + createFlags(0b0000000000000000000100000000); +export const Ref: Flags = createFlags(0b0000000000000000001000000000); +export const Snapshot: Flags = createFlags(0b0000000000000000010000000000); +export const Passive: Flags = createFlags(0b0000000000000000100000000000); +export const Hydrating: Flags = createFlags(0b0000000000000001000000000000); +export const Visibility: Flags = createFlags(0b0000000000000010000000000000); +export const StoreConsistency: Flags = + createFlags(0b0000000000000100000000000000); + +// Combined flags for common operations +export const PlacementAndUpdate: Flags = createFlags( + (Placement as number) | (Update as number), +); +export const Deletion: Flags = createFlags( + (ChildDeletion as number) | (Placement as number), +); + +// Flags that indicate the fiber has work to do in commit phase +export const MutationMask: Flags = createFlags( + (Placement as number) | + (Update as number) | + (ChildDeletion as number) | + (ContentReset as number) | + (Ref as number) | + (Hydrating as number) | + (Visibility as number), +); + +export const LayoutMask: Flags = createFlags( + (Update as number) | (Callback as number) | (Ref as number), +); + +export const PassiveMask: Flags = createFlags( + (Passive as number) | (ChildDeletion as number), +); + +// ============================================ +// Hook Types for Fiber +// ============================================ + +/** + * Effect tag constants for hooks. + * Using const object pattern. + */ +export const HookEffectTag = { + NoEffect: 0b0000, + HasEffect: 0b0001, // Effect needs to run + Layout: 0b0010, // useLayoutEffect + Passive: 0b0100, // useEffect + Insertion: 0b1000, // useInsertionEffect (reserved) +} as const satisfies Record; + +export type HookEffectTag = (typeof HookEffectTag)[keyof typeof HookEffectTag]; + +/** + * Type for effect create callbacks. + * Accepts functions that return a cleanup function or nothing. + * Two-branch union: the second branch allows `() => void` callbacks + * (functions with no return value). + */ +export type EffectCreate = (() => (() => void) | undefined) | (() => void); + +/** + * Effect object stored in fiber's updateQueue for effects. + */ +export type Effect = { + tag: number; // HookEffectTag bitmask + create: EffectCreate; + destroy: (() => void) | undefined; + deps: readonly unknown[] | null; + next: Effect | null; +}; + +/** + * Hook object stored as linked list in fiber.memoizedState. + */ +export type Hook = { + memoizedState: unknown; + baseState: unknown; + baseQueue: Update | null; + queue: UpdateQueue | null; + next: Hook | null; +}; + +/** + * Update object for state updates. + */ +export type Update = { + lane: Lane; + action: S | ((prevState: S) => S); + hasEagerState: boolean; + eagerState: S | null; + next: Update | null; +}; + +/** + * Update queue for a hook. + */ +export type UpdateQueue = { + pending: Update | null; + lanes: Lanes; + dispatch: ((action: S | ((prevState: S) => S)) => void) | null; + lastRenderedReducer: + | ((state: S, action: S | ((prevState: S) => S)) => S) + | null; + lastRenderedState: S | null; +}; + +// ============================================ +// Portal State Node +// ============================================ + +/** + * State node for portal fibers. + * Contains the container element where portal children are rendered. + */ +export type PortalStateNode = { + containerInfo: Element; +}; + +// ============================================ +// Fiber Type - Core data structure +// ============================================ + +/** + * Fiber represents a unit of work in the React reconciler. + * It's a node in a linked tree structure that enables: + * - Incremental rendering (can pause/resume) + * - Priority-based scheduling via lanes + * - Double buffering via alternate pointers + */ +export type Fiber = { + // === Instance Identity === + + /** Type identifier (FunctionComponent, HostComponent, etc.) */ + tag: WorkTag; + + /** Unique key for reconciliation (from JSX key prop) */ + key: string | null; + + /** + * Element type that created this fiber. + * For host components: string ('div', 'span') + * For function components: the function itself + * For class components: the class constructor + */ + elementType: ElementType | null; + + /** + * Resolved type after lazy/forwardRef resolution. + * Usually same as elementType. + */ + type: ElementType | null; + + /** + * Local state associated with this fiber. + * For host components: the DOM node + * For class components: the instance + * For function components: null + * For portals: PortalStateNode with container info + */ + stateNode: Element | Text | FiberRoot | PortalStateNode | null; + + // === Fiber Tree Structure (linked list) === + + /** Parent fiber (called 'return' because work returns to parent) */ + return: Fiber | null; + + /** First child fiber */ + child: Fiber | null; + + /** Next sibling fiber */ + sibling: Fiber | null; + + /** Position in parent's children for placement */ + index: number; + + // === Ref === + + /** Ref attached to this fiber (from JSX ref prop) */ + ref: RefObject | RefCallback | null; + + /** Ref for cleanup (React internal) */ + refCleanup: (() => void) | null; + + // === Props and State === + + /** Props used to create the output (previous render) */ + memoizedProps: Record | null; + + /** Incoming props (next render) */ + pendingProps: Record; + + /** + * State from previous render. + * For function components: linked list of hooks + * For class components: component state + * For host root: element to render + */ + memoizedState: unknown; + + /** + * Queue of state updates and effects. + * For function components: effect list + * For host root: update queue + */ + updateQueue: UpdateQueueType | null; + + // === Context === + + /** Context dependencies for this fiber */ + dependencies: Dependencies | null; + + // === Effects === + + /** Bitmask of side effects (Placement, Update, etc.) */ + flags: Flags; + + /** Subtree flags (bubbled up from children) */ + subtreeFlags: Flags; + + /** Fibers to delete during commit phase */ + deletions: Fiber[] | null; + + // === Lanes (Priority) === + + /** Lanes that have pending work on this fiber */ + lanes: Lanes; + + /** Lanes that have pending work in this fiber's subtree */ + childLanes: Lanes; + + // === Double Buffering === + + /** + * Alternate fiber (current <-> workInProgress). + * Points to the corresponding fiber in the other tree. + */ + alternate: Fiber | null; +}; + +/** + * Ref types + */ +export type RefObject = { current: T | null }; +export type RefCallback = (instance: T | null) => void; + +/** + * Context dependency tracking + */ +export type ContextDependency = { + context: MiniReactContext; + memoizedValue: T; + next: ContextDependency | null; +}; + +export type Dependencies = { + lanes: Lanes; + firstContext: ContextDependency | null; +}; + +/** + * Update queue types + */ +export type UpdateQueueType = { + baseState: unknown; + firstBaseUpdate: Update | null; + lastBaseUpdate: Update | null; + shared: SharedQueue; + effects: Effect[] | null; +}; + +export type SharedQueue = { + pending: Update | null; + lanes: Lanes; +}; + +// ============================================ +// FiberRoot - Root of the entire tree +// ============================================ + +/** + * Root tag identifying the type of root. + */ +export const RootTag = { + LegacyRoot: 0, // ReactDOM.render + ConcurrentRoot: 1, // ReactDOM.createRoot +} as const satisfies Record; + +export type RootTag = (typeof RootTag)[keyof typeof RootTag]; + +/** + * FiberRoot is the top-level container for a fiber tree. + * It holds metadata about the entire tree. + */ +export type FiberRoot = { + /** Type of root (Legacy or Concurrent) */ + tag: RootTag; + + /** DOM container element */ + containerInfo: Element; + + /** Current fiber tree (what's on screen) */ + current: Fiber; + + /** Fiber that's been completed and ready to commit */ + finishedWork: Fiber | null; + + /** Element passed to render() */ + pendingChildren: AnyMiniReactElement | null; + + // === Scheduling === + + /** Lanes with pending work */ + pendingLanes: Lanes; + + /** Lanes that were suspended */ + suspendedLanes: Lanes; + + /** Lanes that were pinged (resumed) */ + pingedLanes: Lanes; + + /** Lanes that have expired and must be rendered synchronously */ + expiredLanes: Lanes; + + /** Lanes currently being rendered */ + finishedLanes: Lanes; + + // === Callbacks === + + /** Callbacks to run after commit */ + callbackNode: unknown; + + /** Priority of the scheduled callback */ + callbackPriority: Lane; + + // === Timing === + + /** Time at which to retry a suspended render */ + expirationTimes: Map; + + // === Hydration === + + /** For hydration: whether we're currently hydrating */ + isDehydrated: boolean; + + /** Mutable source tracking for hydration */ + mutableSourceEagerHydrationData: unknown[] | null; +}; + +// ============================================ +// Type Utilities +// ============================================ + +/** + * Extract fiber for a specific tag (narrowing utility). + */ +export type FiberOfTag = Fiber & { tag: T }; + +/** + * Props type for specific fiber tags. + */ +export type FiberPropsFor = + T extends typeof WorkTag.HostComponent + ? Record & { children?: AnyMiniReactElement[] } + : T extends typeof WorkTag.HostText + ? { nodeValue: string | number } + : T extends typeof WorkTag.FunctionComponent + ? Record & { children?: AnyMiniReactElement[] } + : Record; + +/** + * StateNode type for specific fiber tags. + */ +export type StateNodeFor = + T extends typeof WorkTag.HostComponent + ? Element + : T extends typeof WorkTag.HostText + ? Text + : T extends typeof WorkTag.HostRoot + ? FiberRoot + : null; + +// ============================================ +// Context Import (circular dependency handled) +// ============================================ + +// Forward declare context type to avoid circular import +// The actual implementation is in context/types.ts +export type MiniReactContext = { + $$typeof: symbol; + _currentValue: T; + _defaultValue: T; + Provider: ElementType; + Consumer: ElementType; +}; + +// ============================================ +// Helper Type Guards +// ============================================ + +/** + * Check if a fiber is a host component (has a DOM node). + */ +export function isHostFiber( + fiber: Fiber, +): fiber is Fiber & { stateNode: Element | Text } { + return ( + fiber.tag === WorkTag.HostComponent || + fiber.tag === WorkTag.HostText || + fiber.tag === WorkTag.HostRoot + ); +} + +/** + * Check if a fiber is a function component. + */ +export function isFunctionComponent( + fiber: Fiber, +): fiber is Fiber & { tag: typeof WorkTag.FunctionComponent } { + return fiber.tag === WorkTag.FunctionComponent; +} + +/** + * Check if a fiber is the host root. + */ +export function isHostRoot( + fiber: Fiber, +): fiber is Fiber & { tag: typeof WorkTag.HostRoot; stateNode: FiberRoot } { + return fiber.tag === WorkTag.HostRoot; +} + +/** + * Check if a fiber is a portal. + */ +export function isPortal( + fiber: Fiber, +): fiber is Fiber & { tag: typeof WorkTag.HostPortal } { + return fiber.tag === WorkTag.HostPortal; +} From c1702e0036df40e6940e1469d108b50cd375a841 Mon Sep 17 00:00:00 2001 From: MarcelOlsen Date: Fri, 13 Feb 2026 23:43:50 +0100 Subject: [PATCH 2/5] fix: type defs --- src/core/types.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/core/types.ts b/src/core/types.ts index df5963a..f3e48a5 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -93,6 +93,24 @@ export const PORTAL = Symbol("react.portal"); import type { PortalElement } from "../portals/types"; +// ******************* // +// VDOM Instance Types // +// ******************* // + +export interface VDOMInstance { + element: JSXElementType; + dom: Node | null; + childInstances: VDOMInstance[]; + parent?: VDOMInstance; + hooks?: StateOrEffectHook[]; + hookCursor?: number; + contextValues?: Map; // For context providers + rootContainer?: HTMLElement; // Track root container for root-level instances +} + +// Import hook types from hooks module +import type { StateOrEffectHook } from "../hooks/types"; + // ******************* // // Public Hook Types // // ******************* // From 950542f7f7ceb34f028f0a64bc0c384f33b6c6b3 Mon Sep 17 00:00:00 2001 From: MarcelOlsen Date: Sun, 15 Feb 2026 14:36:35 +0100 Subject: [PATCH 3/5] chore: address review feedback --- src/core/types.ts | 17 +++++------- src/fiber/types.ts | 69 ++++++++++++++++++++++------------------------ 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/src/core/types.ts b/src/core/types.ts index f3e48a5..ec9088e 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -3,6 +3,7 @@ /* **************** */ import type { SyntheticEvent } from "../events/types"; +import type { PortalElement } from "../portals/types"; export type AnyMiniReactElement = | MiniReactElement @@ -91,8 +92,6 @@ export const TEXT_ELEMENT = "TEXT_ELEMENT"; export const FRAGMENT = Symbol("react.fragment"); export const PORTAL = Symbol("react.portal"); -import type { PortalElement } from "../portals/types"; - // ******************* // // VDOM Instance Types // // ******************* // @@ -115,11 +114,9 @@ import type { StateOrEffectHook } from "../hooks/types"; // Public Hook Types // // ******************* // -export type EffectCallback = (() => void) | (() => () => void); -export type DependencyList = readonly unknown[]; - -export type Reducer = (state: State, action: Action) => State; - -export type MutableRefObject = { - current: T; -}; +export type { + EffectCallback, + DependencyList, + Reducer, + MutableRefObject, +} from "../hooks/types"; diff --git a/src/fiber/types.ts b/src/fiber/types.ts index df4cd83..80411c0 100644 --- a/src/fiber/types.ts +++ b/src/fiber/types.ts @@ -64,6 +64,8 @@ export type Flags = number & { readonly [FlagsBrand]: typeof FlagsBrand }; export const createLane = (value: number): Lane => value as Lane; export const createLanes = (value: number): Lanes => value as Lanes; export const createFlags = (value: number): Flags => value as Flags; +export const combineFlags = (...flags: Flags[]): Flags => + createFlags(flags.reduce((acc, f) => acc | (f as number), 0)); // ============================================ // Lane Constants - Priority levels @@ -91,7 +93,7 @@ export const OffscreenLane: Lane = export const NoFlags: Flags = createFlags(0b0000000000000000000000000000); export const PerformedWork: Flags = createFlags(0b0000000000000000000000000001); export const Placement: Flags = createFlags(0b0000000000000000000000000010); -export const Update: Flags = createFlags(0b0000000000000000000000000100); +export const UpdateEffect: Flags = createFlags(0b0000000000000000000000000100); export const ChildDeletion: Flags = createFlags(0b0000000000000000000000010000); export const ContentReset: Flags = createFlags(0b0000000000000000000000100000); export const Callback: Flags = createFlags(0b0000000000000000000001000000); @@ -107,31 +109,23 @@ export const StoreConsistency: Flags = createFlags(0b0000000000000100000000000000); // Combined flags for common operations -export const PlacementAndUpdate: Flags = createFlags( - (Placement as number) | (Update as number), -); -export const Deletion: Flags = createFlags( - (ChildDeletion as number) | (Placement as number), -); +export const PlacementAndUpdate: Flags = combineFlags(Placement, UpdateEffect); +export const Deletion: Flags = combineFlags(ChildDeletion, Placement); // Flags that indicate the fiber has work to do in commit phase -export const MutationMask: Flags = createFlags( - (Placement as number) | - (Update as number) | - (ChildDeletion as number) | - (ContentReset as number) | - (Ref as number) | - (Hydrating as number) | - (Visibility as number), +export const MutationMask: Flags = combineFlags( + Placement, + UpdateEffect, + ChildDeletion, + ContentReset, + Ref, + Hydrating, + Visibility, ); -export const LayoutMask: Flags = createFlags( - (Update as number) | (Callback as number) | (Ref as number), -); +export const LayoutMask: Flags = combineFlags(UpdateEffect, Callback, Ref); -export const PassiveMask: Flags = createFlags( - (Passive as number) | (ChildDeletion as number), -); +export const PassiveMask: Flags = combineFlags(Passive, ChildDeletion); // ============================================ // Hook Types for Fiber @@ -150,6 +144,7 @@ export const HookEffectTag = { } as const satisfies Record; export type HookEffectTag = (typeof HookEffectTag)[keyof typeof HookEffectTag]; +export type HookEffectTagType = HookEffectTag; /** * Type for effect create callbacks. @@ -163,7 +158,7 @@ export type EffectCreate = (() => (() => void) | undefined) | (() => void); * Effect object stored in fiber's updateQueue for effects. */ export type Effect = { - tag: number; // HookEffectTag bitmask + tag: HookEffectTagType; create: EffectCreate; destroy: (() => void) | undefined; deps: readonly unknown[] | null; @@ -254,9 +249,10 @@ export type Fiber = { /** * Local state associated with this fiber. * For host components: the DOM node - * For class components: the instance * For function components: null * For portals: PortalStateNode with container info + * TODO(ClassComponent): Add class component instance type to this union + * when implementing WorkTag.ClassComponent support. */ stateNode: Element | Text | FiberRoot | PortalStateNode | null; @@ -312,7 +308,7 @@ export type Fiber = { // === Effects === - /** Bitmask of side effects (Placement, Update, etc.) */ + /** Bitmask of side effects (Placement, UpdateEffect, etc.) */ flags: Flags; /** Subtree flags (bubbled up from children) */ @@ -481,29 +477,30 @@ export type StateNodeFor = : null; // ============================================ -// Context Import (circular dependency handled) +// Context Import (type-only to avoid circular dependency) // ============================================ -// Forward declare context type to avoid circular import -// The actual implementation is in context/types.ts -export type MiniReactContext = { - $$typeof: symbol; - _currentValue: T; - _defaultValue: T; - Provider: ElementType; - Consumer: ElementType; -}; +import type { MiniReactContext } from "../context/types"; +export type { MiniReactContext }; // ============================================ // Helper Type Guards // ============================================ /** - * Check if a fiber is a host component (has a DOM node). + * Check if a fiber represents a DOM element or text node. + * Use this when you need to perform DOM operations on fiber.stateNode. */ -export function isHostFiber( +export function isDOMFiber( fiber: Fiber, ): fiber is Fiber & { stateNode: Element | Text } { + return fiber.tag === WorkTag.HostComponent || fiber.tag === WorkTag.HostText; +} + +/** + * Check if a fiber is a host fiber (DOM element, text node, or host root). + */ +export function isHostFiber(fiber: Fiber): boolean { return ( fiber.tag === WorkTag.HostComponent || fiber.tag === WorkTag.HostText || From f7af613b136842f281d9a6b666d425ab7e6d4020 Mon Sep 17 00:00:00 2001 From: MarcelOlsen Date: Sun, 15 Feb 2026 16:30:56 +0100 Subject: [PATCH 4/5] chore: address further review feedback --- src/core/types.ts | 4 +--- src/fiber/types.ts | 10 +++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/core/types.ts b/src/core/types.ts index ec9088e..f916de5 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -3,6 +3,7 @@ /* **************** */ import type { SyntheticEvent } from "../events/types"; +import type { StateOrEffectHook } from "../hooks/types"; import type { PortalElement } from "../portals/types"; export type AnyMiniReactElement = @@ -107,9 +108,6 @@ export interface VDOMInstance { rootContainer?: HTMLElement; // Track root container for root-level instances } -// Import hook types from hooks module -import type { StateOrEffectHook } from "../hooks/types"; - // ******************* // // Public Hook Types // // ******************* // diff --git a/src/fiber/types.ts b/src/fiber/types.ts index 80411c0..e683fa2 100644 --- a/src/fiber/types.ts +++ b/src/fiber/types.ts @@ -144,7 +144,6 @@ export const HookEffectTag = { } as const satisfies Record; export type HookEffectTag = (typeof HookEffectTag)[keyof typeof HookEffectTag]; -export type HookEffectTagType = HookEffectTag; /** * Type for effect create callbacks. @@ -158,7 +157,7 @@ export type EffectCreate = (() => (() => void) | undefined) | (() => void); * Effect object stored in fiber's updateQueue for effects. */ export type Effect = { - tag: HookEffectTagType; + tag: HookEffectTag; create: EffectCreate; destroy: (() => void) | undefined; deps: readonly unknown[] | null; @@ -529,8 +528,9 @@ export function isHostRoot( /** * Check if a fiber is a portal. */ -export function isPortal( - fiber: Fiber, -): fiber is Fiber & { tag: typeof WorkTag.HostPortal } { +export function isPortal(fiber: Fiber): fiber is Fiber & { + tag: typeof WorkTag.HostPortal; + stateNode: PortalStateNode; +} { return fiber.tag === WorkTag.HostPortal; } From 2e73945decbcb3ffc7d6de4cdbd402fa59e4a7b7 Mon Sep 17 00:00:00 2001 From: MarcelOlsen Date: Sun, 15 Feb 2026 16:40:03 +0100 Subject: [PATCH 5/5] fix: add WorkTag.HostPortal to StateNodeFor type --- src/fiber/types.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/fiber/types.ts b/src/fiber/types.ts index e683fa2..0fa4b7e 100644 --- a/src/fiber/types.ts +++ b/src/fiber/types.ts @@ -473,7 +473,9 @@ export type StateNodeFor = ? Text : T extends typeof WorkTag.HostRoot ? FiberRoot - : null; + : T extends typeof WorkTag.HostPortal + ? PortalStateNode + : null; // ============================================ // Context Import (type-only to avoid circular dependency)