Add Effect v4 support, Fast Refresh tooling, and revamped docs #56
@@ -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.
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true,
|
"noUncheckedSideEffectImports": true,
|
||||||
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user