From 9a93d72e48a67bc008803bca5e5eaf6d38eecdc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Mon, 20 Jul 2026 20:40:13 +0200 Subject: [PATCH] Change useContextFromLayer to useLayer --- packages/docs/docs/getting-started.md | 8 ++++---- packages/effect-fc-next/README.md | 2 +- packages/effect-fc-next/src/Component.ts | 12 +++++------ .../effect-fc-next/test/Component.test.tsx | 2 +- packages/effect-fc/README.md | 2 +- packages/effect-fc/src/Component.ts | 20 +++++++------------ packages/effect-fc/test/Component.test.tsx | 2 +- packages/example-next/src/routes/lensform.tsx | 2 +- packages/example/package.json | 2 +- packages/example/src/routes/dev/context.tsx | 2 +- packages/example/src/routes/form.tsx | 2 +- packages/example/src/routes/index.tsx | 2 +- packages/example/tsconfig.app.json | 1 - 13 files changed, 25 insertions(+), 34 deletions(-) diff --git a/packages/docs/docs/getting-started.md b/packages/docs/docs/getting-started.md index 7a9b67b..e941385 100644 --- a/packages/docs/docs/getting-started.md +++ b/packages/docs/docs/getting-started.md @@ -370,7 +370,7 @@ that behavior, pass their tags with `Component.withOptions({ nonReactiveTags: ## Provide Services To A Subtree -Use `Component.useContextFromLayer` when an Effect View component should provide +Use `Component.useLayer` when an Effect View component should provide extra services to another Effect View component. It turns a `Layer` into a runtime context that can be provided to `.use`. @@ -381,19 +381,19 @@ import { GreetingView } from "./Greeting" import { GreetingLive } from "./services" export const GreetingPageView = Component.make("GreetingPage")(function* () { - const context = yield* Component.useContextFromLayer(GreetingLive) + const context = yield* Component.useLayer(GreetingLive) const Greeting = yield* GreetingView.use.pipe(Effect.provide(context)) return }) ``` -The layer passed to `useContextFromLayer` should be stable. If a new layer value +The layer passed to `useLayer` should be stable. If a new layer value is created on every render, Effect View has to build a new context, which can cause unnecessary re-renders and scoped lifecycle restarts. Define static layers outside the component, or memoize layers that depend on React props or state. -Layers built with `useContextFromLayer` are scoped to the component that builds +Layers built with `useLayer` are scoped to the component that builds them. That means service construction can register scoped logic, acquire resources, fork scoped fibers, and add finalizers that are tied to that component's lifecycle. diff --git a/packages/effect-fc-next/README.md b/packages/effect-fc-next/README.md index 2ce8f10..1c613a0 100644 --- a/packages/effect-fc-next/README.md +++ b/packages/effect-fc-next/README.md @@ -42,7 +42,7 @@ export class TodosView extends Component.make("TodosView")(function*() { }) {} const Index = Component.make("IndexView")(function*() { - const context = yield* Component.useContextFromLayer(TodosState.Default) + const context = yield* Component.useLayer(TodosState.Default) const Todos = yield* Effect.provide(TodosView.use, context) return diff --git a/packages/effect-fc-next/src/Component.ts b/packages/effect-fc-next/src/Component.ts index 8aaa622..c9a45be 100644 --- a/packages/effect-fc-next/src/Component.ts +++ b/packages/effect-fc-next/src/Component.ts @@ -1099,7 +1099,7 @@ export declare namespace useContext { * ```tsx * const MyLayer = Layer.succeed(MyService, new MyServiceImpl()) * const MyComponent = Component.make(function*() { - * const context = yield* Component.useContextFromLayer(MyLayer) + * const context = yield* Component.useLayer(MyLayer) * const Sub = yield* SubComponent.use.pipe( * Effect.provide(context) * ) @@ -1111,7 +1111,7 @@ export declare namespace useContext { * @example With memoized layer * ```tsx * const MyComponent = Component.make(function*(props: { id: string })) { - * const context = yield* Component.useContextFromLayer( + * const context = yield* Component.useLayer( * React.useMemo(() => Layer.succeed(MyService, new MyServiceImpl(props.id)), [props.id]) * ) * const Sub = yield* SubComponent.use.pipe( @@ -1126,17 +1126,15 @@ export declare namespace useContext { * ```tsx * const MyAsyncLayer = Layer.effect(MyService, someAsyncEffect) * const MyComponent = Component.make(function*() { - * const context = yield* Component.useContextFromLayer(MyAsyncLayer) - * const Sub = yield* SubComponent.use.pipe( - * Effect.provide(context) - * ) + * const context = yield* Component.useLayer(MyAsyncLayer) + * const Sub = yield* Effect.provide(SubComponent, context) * * return * }).pipe( * Async.async // Required to handle async layer effects * ) */ -export const useContextFromLayer = ( +export const useLayer = ( layer: Layer.Layer, options?: useContext.Options, ): Effect.Effect, E, RIn | Scope.Scope> => useOnChange(() => Effect.flatMap( diff --git a/packages/effect-fc-next/test/Component.test.tsx b/packages/effect-fc-next/test/Component.test.tsx index 0abd55e..34393f0 100644 --- a/packages/effect-fc-next/test/Component.test.tsx +++ b/packages/effect-fc-next/test/Component.test.tsx @@ -317,7 +317,7 @@ describe("Component", () => { () => Layer.succeed(ValueService, { value: props.value }), [props.value], ) - const context = yield* Component.useContextFromLayer(serviceLayer, { + const context = yield* Component.useLayer(serviceLayer, { finalizerExecutionDebounce: 0, }) const Child = yield* Effect.provide(SubComponent.use, context) diff --git a/packages/effect-fc/README.md b/packages/effect-fc/README.md index d35c93d..4a9e717 100644 --- a/packages/effect-fc/README.md +++ b/packages/effect-fc/README.md @@ -42,7 +42,7 @@ export class TodosView extends Component.make("TodosView")(function*() { }) {} const Index = Component.make("IndexView")(function*() { - const context = yield* Component.useContextFromLayer(TodosState.Default) + const context = yield* Component.useLayer(TodosState.Default) const Todos = yield* Effect.provide(TodosView.use, context) return diff --git a/packages/effect-fc/src/Component.ts b/packages/effect-fc/src/Component.ts index ef54a5b..91df15e 100644 --- a/packages/effect-fc/src/Component.ts +++ b/packages/effect-fc/src/Component.ts @@ -1075,10 +1075,8 @@ export declare namespace useContext { * ```tsx * const MyLayer = Layer.succeed(MyService, new MyServiceImpl()) * const MyComponent = Component.make(function*() { - * const context = yield* Component.useContextFromLayer(MyLayer) - * const Sub = yield* SubComponent.use.pipe( - * Effect.provide(context) - * ) + * const context = yield* Component.useLayer(MyLayer) + * const Sub = yield* Effect.provide(SubComponent.use, context) * * return * }) @@ -1087,12 +1085,10 @@ export declare namespace useContext { * @example With memoized layer * ```tsx * const MyComponent = Component.make(function*(props: { id: string })) { - * const context = yield* Component.useContextFromLayer( + * const context = yield* Component.useLayer( * React.useMemo(() => Layer.succeed(MyService, new MyServiceImpl(props.id)), [props.id]) * ) - * const Sub = yield* SubComponent.use.pipe( - * Effect.provide(context) - * ) + * const Sub = yield* Effect.provide(SubComponent.use, context) * * return * }) @@ -1102,17 +1098,15 @@ export declare namespace useContext { * ```tsx * const MyAsyncLayer = Layer.effect(MyService, someAsyncEffect) * const MyComponent = Component.make(function*() { - * const context = yield* Component.useContextFromLayer(MyAsyncLayer) - * const Sub = yield* SubComponent.use.pipe( - * Effect.provide(context) - * ) + * const context = yield* Component.useLayer(MyAsyncLayer) + * const Sub = yield* Effect.provide(SubComponent.use, context) * * return * }).pipe( * Async.async // Required to handle async layer effects * ) */ -export const useContextFromLayer = ( +export const useLayer = ( layer: Layer.Layer, options?: useContext.Options, ): Effect.Effect, E, RIn | Scope.Scope> => useOnChange(() => Effect.context().pipe( diff --git a/packages/effect-fc/test/Component.test.tsx b/packages/effect-fc/test/Component.test.tsx index eb846d9..f870806 100644 --- a/packages/effect-fc/test/Component.test.tsx +++ b/packages/effect-fc/test/Component.test.tsx @@ -317,7 +317,7 @@ describe("Component", () => { () => Layer.succeed(ValueService, { value: props.value }), [props.value], ) - const context = yield* Component.useContextFromLayer(serviceLayer, { + const context = yield* Component.useLayer(serviceLayer, { finalizerExecutionDebounce: 0, }) const Child = yield* Effect.provide(SubComponent.use, context) diff --git a/packages/example-next/src/routes/lensform.tsx b/packages/example-next/src/routes/lensform.tsx index aa1cae0..8b5b037 100644 --- a/packages/example-next/src/routes/lensform.tsx +++ b/packages/example-next/src/routes/lensform.tsx @@ -47,7 +47,7 @@ const LensFormPageView = Component.make("LensFormPageView")(function*() { yield* Console.log("LensForm route mounted") })) - const context = yield* Component.useContextFromLayer(AppState.layer) + const context = yield* Component.useLayer(AppState.layer) const UserProfileEditor = yield* Effect.provide(UserProfileEditorView.use, context) return diff --git a/packages/example/package.json b/packages/example/package.json index e2b05c7..141145e 100644 --- a/packages/example/package.json +++ b/packages/example/package.json @@ -5,7 +5,7 @@ "private": true, "scripts": { "dev": "vite", - "lint:tsc": "tsc --noEmit", + "lint:tsc": "tsc -b --noEmit", "lint:biome": "biome lint", "preview": "vite preview", "clean:cache": "rm -rf .turbo node_modules/.tmp node_modules/.vite* .tanstack", diff --git a/packages/example/src/routes/dev/context.tsx b/packages/example/src/routes/dev/context.tsx index 26048a9..7e5e600 100644 --- a/packages/example/src/routes/dev/context.tsx +++ b/packages/example/src/routes/dev/context.tsx @@ -23,7 +23,7 @@ const SubComponent = Component.makeUntraced("SubComponent")(function*() { const ContextView = Component.makeUntraced("ContextView")(function*() { const [serviceValue, setServiceValue] = React.useState("test") const SubServiceLayer = React.useMemo(() => SubService.Default(serviceValue), [serviceValue]) - const SubComponentFC = yield* Effect.provide(SubComponent.use, yield* Component.useContextFromLayer(SubServiceLayer)) + const SubComponentFC = yield* Effect.provide(SubComponent.use, yield* Component.useLayer(SubServiceLayer)) return ( diff --git a/packages/example/src/routes/form.tsx b/packages/example/src/routes/form.tsx index 9ce3fae..cde4eda 100644 --- a/packages/example/src/routes/form.tsx +++ b/packages/example/src/routes/form.tsx @@ -129,7 +129,7 @@ class RegisterFormView extends Component.make("RegisterFormView")(function*() { const RegisterPage = Component.make("RegisterPageView")(function*() { const RegisterForm = yield* Effect.provide( RegisterFormView.use, - yield* Component.useContextFromLayer(RegisterFormService.Default), + yield* Component.useLayer(RegisterFormService.Default), ) return diff --git a/packages/example/src/routes/index.tsx b/packages/example/src/routes/index.tsx index d747b39..b4582f3 100644 --- a/packages/example/src/routes/index.tsx +++ b/packages/example/src/routes/index.tsx @@ -11,7 +11,7 @@ const TodosStateLive = TodosState.Default("todos") const Index = Component.make("IndexView")(function*() { const Todos = yield* Effect.provide( TodosView.use, - yield* Component.useContextFromLayer(TodosStateLive), + yield* Component.useLayer(TodosStateLive), ) return diff --git a/packages/example/tsconfig.app.json b/packages/example/tsconfig.app.json index 835427f..b823bd2 100644 --- a/packages/example/tsconfig.app.json +++ b/packages/example/tsconfig.app.json @@ -22,7 +22,6 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true, - "baseUrl": ".", "paths": { "@/*": ["./src/*"] },