@@ -1,6 +1,6 @@
|
|||||||
/** biome-ignore-all lint/complexity/noBannedTypes: {} is the default type for React props */
|
/** biome-ignore-all lint/complexity/noBannedTypes: {} is the default type for React props */
|
||||||
/** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */
|
/** 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 React from "react"
|
||||||
import * as ScopeRegistry from "./ScopeRegistry.js"
|
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.
|
* 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>[]
|
readonly nonReactiveTags: readonly Context.Key<any, any>[]
|
||||||
|
|
||||||
@@ -163,7 +163,13 @@ export interface ComponentOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const defaultOptions: 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",
|
finalizerExecutionStrategy: "sequential",
|
||||||
finalizerExecutionDebounce: "100 millis",
|
finalizerExecutionDebounce: "100 millis",
|
||||||
scopeCommitTimeout: "1 minute",
|
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