Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 53 additions & 53 deletions src/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,17 @@
/* ***************** */

import { createElement } from "../core";
import type {
AnyMiniReactElement,
FunctionalComponent,
VDOMInstance,
} from "../core/types";
import type { AnyMiniReactElement, FunctionalComponent } from "../core/types";
import { getCurrentlyRenderingFiber } from "../fiber/fiberHooks";
import type { Fiber } from "../fiber/types";
import type { MiniReactContext } from "./types";

// Context system - Track context providers in the render tree
const contextStack: Map<symbol, unknown>[] = [];

// Hook state management
let currentRenderInstance: VDOMInstance | null = null;

/**
* Sets the current render instance for context hooks
* @param instance The current VDOM instance
*/
export function setContextRenderInstance(instance: VDOMInstance | null): void {
currentRenderInstance = instance;
}

/**
* Pushes a context value onto the context stack
* @param contextValues The context values to push
*/
export function pushContextValues(contextValues: Map<symbol, unknown>): void {
contextStack.push(contextValues);
}

/**
* Pops a context value from the context stack
*/
export function popContextValues(): void {
contextStack.pop();
}
// Map from Provider function to its context object
// Used to identify Provider fibers when traversing the tree
const providerToContext = new WeakMap<
FunctionalComponent<{ value: unknown; children?: AnyMiniReactElement[] }>,
MiniReactContext<unknown>
>();

/**
* createContext function - Creates a new context object with default value
Expand All @@ -61,14 +37,6 @@ export function createContext<T>(defaultValue: T): MiniReactContext<T> {
// Update the context's current value to keep it in sync
context._currentValue = value;

// Store context value in the current instance so reconciler can manage context stack
if (currentRenderInstance) {
if (!currentRenderInstance.contextValues) {
currentRenderInstance.contextValues = new Map();
}
currentRenderInstance.contextValues.set(contextId, value);
}

// Render children
let result: AnyMiniReactElement | null = null;
if (!children || children.length === 0) {
Expand All @@ -89,28 +57,60 @@ export function createContext<T>(defaultValue: T): MiniReactContext<T> {
// Set the Provider function on the context object
context.Provider = Provider;

// Register Provider function with its context for fiber traversal lookup
providerToContext.set(
Provider as FunctionalComponent<{
value: unknown;
children?: AnyMiniReactElement[];
}>,
context as MiniReactContext<unknown>,
);

return context;
}

/**
* Finds the context value by traversing up the fiber tree.
* Returns the value from the nearest Provider or the default value.
*/
function findContextValue<T>(fiber: Fiber, context: MiniReactContext<T>): T {
let currentFiber: Fiber | null = fiber.return;

while (currentFiber !== null) {
// Check if this fiber is a Provider for our context
const fiberType = currentFiber.type;
if (typeof fiberType === "function") {
const providerContext = providerToContext.get(
fiberType as FunctionalComponent<{
value: unknown;
children?: AnyMiniReactElement[];
}>,
);
if (providerContext === (context as MiniReactContext<unknown>)) {
// Found the Provider! Get the value from its props
const props = currentFiber.memoizedProps ?? currentFiber.pendingProps;
if (props && "value" in props) {
return props["value"] as T;
}
}
}
currentFiber = currentFiber.return;
}

// No Provider found, return default value
return context._defaultValue;
}

/**
* useContext hook implementation
* @param context The context object created by createContext
* @returns The current context value
*/
export function useContext<T>(context: MiniReactContext<T>): T {
if (!currentRenderInstance) {
const currentFiber = getCurrentlyRenderingFiber();
if (currentFiber === null) {
throw new Error("useContext must be called inside a functional component");
}

// Check the global context stack for active context values
for (let i = contextStack.length - 1; i >= 0; i--) {
const contextMap = contextStack[i];
if (contextMap.has(context._contextId)) {
const value = contextMap.get(context._contextId) as T;
return value;
}
}

// Return default value if no provider found
return context._defaultValue;
return findContextValue(currentFiber, context);
}
Loading
Loading