Add Effect v4 support, Fast Refresh tooling, and revamped docs #56
@@ -1,6 +1,6 @@
|
||||
/** biome-ignore-all lint/complexity/noBannedTypes: {} is the default type for React props */
|
||||
/** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */
|
||||
import { Context, type Duration, Effect, Equivalence, Exit, Function, identity, Layer, Pipeable, Predicate, Scheduler, Scope, Tracer } from "effect"
|
||||
import { Context, type Duration, Effect, Equivalence, Exit, Function, identity, Layer, Pipeable, Predicate, References, Scheduler, Scope, Tracer } from "effect"
|
||||
import * as React from "react"
|
||||
import * as ScopeRegistry from "./ScopeRegistry.js"
|
||||
|
||||
@@ -133,7 +133,7 @@ export interface ComponentOptions {
|
||||
/**
|
||||
* Context tags that should not trigger component remount when their values change.
|
||||
*
|
||||
* @default [Tracer.ParentSpan]
|
||||
* @default [Tracer.ParentSpan, References.CurrentStackFrame, Scheduler.Scheduler, Layer.CurrentMemoMap, Scope.Scope]
|
||||
*/
|
||||
readonly nonReactiveTags: readonly Context.Key<any, any>[]
|
||||
|
||||
@@ -163,7 +163,13 @@ export interface ComponentOptions {
|
||||
}
|
||||
|
||||
export const defaultOptions: ComponentOptions = {
|
||||
nonReactiveTags: [Tracer.ParentSpan, Scheduler.Scheduler, Layer.CurrentMemoMap],
|
||||
nonReactiveTags: [
|
||||
Tracer.ParentSpan,
|
||||
References.CurrentStackFrame,
|
||||
Scheduler.Scheduler,
|
||||
Layer.CurrentMemoMap,
|
||||
Scope.Scope,
|
||||
],
|
||||
finalizerExecutionStrategy: "sequential",
|
||||
finalizerExecutionDebounce: "100 millis",
|
||||
scopeCommitTimeout: "1 minute",
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { act, fireEvent, render, screen } from "@testing-library/react"
|
||||
import { Effect, Layer } from "effect"
|
||||
import * as React from "react"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import * as Async from "../src/Async.js"
|
||||
import * as Component from "../src/Component.js"
|
||||
import * as Memoized from "../src/Memoized.js"
|
||||
import * as ReactRuntime from "../src/ReactRuntime.js"
|
||||
|
||||
describe("Async", () => {
|
||||
it("does not rerun for an unrelated parent state update", async () => {
|
||||
const load = vi.fn((_id: number) => Effect.never)
|
||||
const renderPost = vi.fn()
|
||||
const runtime = ReactRuntime.make(Layer.empty)
|
||||
const context = await runtime.runtime.context()
|
||||
|
||||
const Post = Component.make("Post")(function*(props: { readonly id: number }) {
|
||||
renderPost()
|
||||
const value = yield* Component.useOnChange(() => load(props.id), [props.id])
|
||||
return <div>{value}</div>
|
||||
}).pipe(
|
||||
Async.async,
|
||||
Memoized.memoized,
|
||||
)
|
||||
|
||||
const Parent = Component.make("Parent")(function*() {
|
||||
const [text, setText] = React.useState("")
|
||||
const AsyncPost = yield* Post.use
|
||||
|
||||
return <>
|
||||
<input
|
||||
aria-label="text"
|
||||
value={text}
|
||||
onChange={event => setText(event.currentTarget.value)}
|
||||
/>
|
||||
<AsyncPost id={1} fallback={<div>loading</div>} />
|
||||
</>
|
||||
}).pipe(Component.withContext(runtime.context))
|
||||
|
||||
let view!: ReturnType<typeof render>
|
||||
await act(async () => {
|
||||
view = render(
|
||||
<runtime.context.Provider value={context}>
|
||||
<Parent />
|
||||
</runtime.context.Provider>,
|
||||
)
|
||||
})
|
||||
|
||||
expect(screen.getByText("loading")).toBeTruthy()
|
||||
expect(load).toHaveBeenCalledTimes(2)
|
||||
const callsAfterLoad = load.mock.calls.length
|
||||
const rendersAfterLoad = renderPost.mock.calls.length
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.change(screen.getByLabelText("text"), { target: { value: "a" } })
|
||||
})
|
||||
expect(load).toHaveBeenCalledTimes(callsAfterLoad)
|
||||
expect(renderPost).toHaveBeenCalledTimes(rendersAfterLoad)
|
||||
|
||||
view.unmount()
|
||||
await runtime.runtime.dispose()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user