Change useContextFromLayer to useLayer
Lint / lint (push) Failing after 43s

This commit is contained in:
Julien Valverdé
2026-07-20 20:40:13 +02:00
parent 19d5aba009
commit 9a93d72e48
13 changed files with 25 additions and 34 deletions
+4 -4
View File
@@ -370,7 +370,7 @@ that behavior, pass their tags with `Component.withOptions({ nonReactiveTags:
## Provide Services To A Subtree ## 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 extra services to another Effect View component. It turns a `Layer` into a runtime
context that can be provided to `.use`. context that can be provided to `.use`.
@@ -381,19 +381,19 @@ import { GreetingView } from "./Greeting"
import { GreetingLive } from "./services" import { GreetingLive } from "./services"
export const GreetingPageView = Component.make("GreetingPage")(function* () { 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)) const Greeting = yield* GreetingView.use.pipe(Effect.provide(context))
return <Greeting name="Effect" /> return <Greeting name="Effect" />
}) })
``` ```
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 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 cause unnecessary re-renders and scoped lifecycle restarts. Define static layers
outside the component, or memoize layers that depend on React props or state. 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 them. That means service construction can register scoped logic, acquire
resources, fork scoped fibers, and add finalizers that are tied to that resources, fork scoped fibers, and add finalizers that are tied to that
component's lifecycle. component's lifecycle.
+1 -1
View File
@@ -42,7 +42,7 @@ export class TodosView extends Component.make("TodosView")(function*() {
}) {} }) {}
const Index = Component.make("IndexView")(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) const Todos = yield* Effect.provide(TodosView.use, context)
return <Todos /> return <Todos />
+5 -7
View File
@@ -1099,7 +1099,7 @@ export declare namespace useContext {
* ```tsx * ```tsx
* const MyLayer = Layer.succeed(MyService, new MyServiceImpl()) * const MyLayer = Layer.succeed(MyService, new MyServiceImpl())
* const MyComponent = Component.make(function*() { * const MyComponent = Component.make(function*() {
* const context = yield* Component.useContextFromLayer(MyLayer) * const context = yield* Component.useLayer(MyLayer)
* const Sub = yield* SubComponent.use.pipe( * const Sub = yield* SubComponent.use.pipe(
* Effect.provide(context) * Effect.provide(context)
* ) * )
@@ -1111,7 +1111,7 @@ export declare namespace useContext {
* @example With memoized layer * @example With memoized layer
* ```tsx * ```tsx
* const MyComponent = Component.make(function*(props: { id: string })) { * 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]) * React.useMemo(() => Layer.succeed(MyService, new MyServiceImpl(props.id)), [props.id])
* ) * )
* const Sub = yield* SubComponent.use.pipe( * const Sub = yield* SubComponent.use.pipe(
@@ -1126,17 +1126,15 @@ export declare namespace useContext {
* ```tsx * ```tsx
* const MyAsyncLayer = Layer.effect(MyService, someAsyncEffect) * const MyAsyncLayer = Layer.effect(MyService, someAsyncEffect)
* const MyComponent = Component.make(function*() { * const MyComponent = Component.make(function*() {
* const context = yield* Component.useContextFromLayer(MyAsyncLayer) * const context = yield* Component.useLayer(MyAsyncLayer)
* const Sub = yield* SubComponent.use.pipe( * const Sub = yield* Effect.provide(SubComponent, context)
* Effect.provide(context)
* )
* *
* return <Sub /> * return <Sub />
* }).pipe( * }).pipe(
* Async.async // Required to handle async layer effects * Async.async // Required to handle async layer effects
* ) * )
*/ */
export const useContextFromLayer = <ROut, E, RIn>( export const useLayer = <ROut, E, RIn>(
layer: Layer.Layer<ROut, E, RIn>, layer: Layer.Layer<ROut, E, RIn>,
options?: useContext.Options, options?: useContext.Options,
): Effect.Effect<Context.Context<ROut>, E, RIn | Scope.Scope> => useOnChange(() => Effect.flatMap( ): Effect.Effect<Context.Context<ROut>, E, RIn | Scope.Scope> => useOnChange(() => Effect.flatMap(
@@ -317,7 +317,7 @@ describe("Component", () => {
() => Layer.succeed(ValueService, { value: props.value }), () => Layer.succeed(ValueService, { value: props.value }),
[props.value], [props.value],
) )
const context = yield* Component.useContextFromLayer(serviceLayer, { const context = yield* Component.useLayer(serviceLayer, {
finalizerExecutionDebounce: 0, finalizerExecutionDebounce: 0,
}) })
const Child = yield* Effect.provide(SubComponent.use, context) const Child = yield* Effect.provide(SubComponent.use, context)
+1 -1
View File
@@ -42,7 +42,7 @@ export class TodosView extends Component.make("TodosView")(function*() {
}) {} }) {}
const Index = Component.make("IndexView")(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) const Todos = yield* Effect.provide(TodosView.use, context)
return <Todos /> return <Todos />
+7 -13
View File
@@ -1075,10 +1075,8 @@ export declare namespace useContext {
* ```tsx * ```tsx
* const MyLayer = Layer.succeed(MyService, new MyServiceImpl()) * const MyLayer = Layer.succeed(MyService, new MyServiceImpl())
* const MyComponent = Component.make(function*() { * const MyComponent = Component.make(function*() {
* const context = yield* Component.useContextFromLayer(MyLayer) * const context = yield* Component.useLayer(MyLayer)
* const Sub = yield* SubComponent.use.pipe( * const Sub = yield* Effect.provide(SubComponent.use, context)
* Effect.provide(context)
* )
* *
* return <Sub /> * return <Sub />
* }) * })
@@ -1087,12 +1085,10 @@ export declare namespace useContext {
* @example With memoized layer * @example With memoized layer
* ```tsx * ```tsx
* const MyComponent = Component.make(function*(props: { id: string })) { * 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]) * React.useMemo(() => Layer.succeed(MyService, new MyServiceImpl(props.id)), [props.id])
* ) * )
* const Sub = yield* SubComponent.use.pipe( * const Sub = yield* Effect.provide(SubComponent.use, context)
* Effect.provide(context)
* )
* *
* return <Sub /> * return <Sub />
* }) * })
@@ -1102,17 +1098,15 @@ export declare namespace useContext {
* ```tsx * ```tsx
* const MyAsyncLayer = Layer.effect(MyService, someAsyncEffect) * const MyAsyncLayer = Layer.effect(MyService, someAsyncEffect)
* const MyComponent = Component.make(function*() { * const MyComponent = Component.make(function*() {
* const context = yield* Component.useContextFromLayer(MyAsyncLayer) * const context = yield* Component.useLayer(MyAsyncLayer)
* const Sub = yield* SubComponent.use.pipe( * const Sub = yield* Effect.provide(SubComponent.use, context)
* Effect.provide(context)
* )
* *
* return <Sub /> * return <Sub />
* }).pipe( * }).pipe(
* Async.async // Required to handle async layer effects * Async.async // Required to handle async layer effects
* ) * )
*/ */
export const useContextFromLayer = <ROut, E, RIn>( export const useLayer = <ROut, E, RIn>(
layer: Layer.Layer<ROut, E, RIn>, layer: Layer.Layer<ROut, E, RIn>,
options?: useContext.Options, options?: useContext.Options,
): Effect.Effect<Context.Context<ROut>, E, RIn | Scope.Scope> => useOnChange(() => Effect.context<RIn>().pipe( ): Effect.Effect<Context.Context<ROut>, E, RIn | Scope.Scope> => useOnChange(() => Effect.context<RIn>().pipe(
+1 -1
View File
@@ -317,7 +317,7 @@ describe("Component", () => {
() => Layer.succeed(ValueService, { value: props.value }), () => Layer.succeed(ValueService, { value: props.value }),
[props.value], [props.value],
) )
const context = yield* Component.useContextFromLayer(serviceLayer, { const context = yield* Component.useLayer(serviceLayer, {
finalizerExecutionDebounce: 0, finalizerExecutionDebounce: 0,
}) })
const Child = yield* Effect.provide(SubComponent.use, context) const Child = yield* Effect.provide(SubComponent.use, context)
@@ -47,7 +47,7 @@ const LensFormPageView = Component.make("LensFormPageView")(function*() {
yield* Console.log("LensForm route mounted") 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) const UserProfileEditor = yield* Effect.provide(UserProfileEditorView.use, context)
return <UserProfileEditor /> return <UserProfileEditor />
+1 -1
View File
@@ -5,7 +5,7 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"lint:tsc": "tsc --noEmit", "lint:tsc": "tsc -b --noEmit",
"lint:biome": "biome lint", "lint:biome": "biome lint",
"preview": "vite preview", "preview": "vite preview",
"clean:cache": "rm -rf .turbo node_modules/.tmp node_modules/.vite* .tanstack", "clean:cache": "rm -rf .turbo node_modules/.tmp node_modules/.vite* .tanstack",
+1 -1
View File
@@ -23,7 +23,7 @@ const SubComponent = Component.makeUntraced("SubComponent")(function*() {
const ContextView = Component.makeUntraced("ContextView")(function*() { const ContextView = Component.makeUntraced("ContextView")(function*() {
const [serviceValue, setServiceValue] = React.useState("test") const [serviceValue, setServiceValue] = React.useState("test")
const SubServiceLayer = React.useMemo(() => SubService.Default(serviceValue), [serviceValue]) 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 ( return (
<Container> <Container>
+1 -1
View File
@@ -129,7 +129,7 @@ class RegisterFormView extends Component.make("RegisterFormView")(function*() {
const RegisterPage = Component.make("RegisterPageView")(function*() { const RegisterPage = Component.make("RegisterPageView")(function*() {
const RegisterForm = yield* Effect.provide( const RegisterForm = yield* Effect.provide(
RegisterFormView.use, RegisterFormView.use,
yield* Component.useContextFromLayer(RegisterFormService.Default), yield* Component.useLayer(RegisterFormService.Default),
) )
return <RegisterForm /> return <RegisterForm />
+1 -1
View File
@@ -11,7 +11,7 @@ const TodosStateLive = TodosState.Default("todos")
const Index = Component.make("IndexView")(function*() { const Index = Component.make("IndexView")(function*() {
const Todos = yield* Effect.provide( const Todos = yield* Effect.provide(
TodosView.use, TodosView.use,
yield* Component.useContextFromLayer(TodosStateLive), yield* Component.useLayer(TodosStateLive),
) )
return <Todos /> return <Todos />
-1
View File
@@ -22,7 +22,6 @@
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true, "noUncheckedSideEffectImports": true,
"baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
}, },