mirror of
https://github.com/re-rxjs/react-rxjs.git
synced 2025-12-08 18:01:51 +00:00
Add getServerSnapshot, fix loop on SSR Subscribe (#306)
This commit is contained in:
parent
963ff9488f
commit
d0d089ad63
13
package-lock.json
generated
13
package-lock.json
generated
@ -12,6 +12,7 @@
|
||||
"@babel/preset-env": "^7.22.7",
|
||||
"@babel/preset-typescript": "^7.22.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@types/node": "^20.4.7",
|
||||
"@types/react": "^18.2.14",
|
||||
"@types/react-dom": "^18.2.6",
|
||||
"@vitest/coverage-v8": "^0.33.0",
|
||||
@ -2545,9 +2546,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.1.tgz",
|
||||
"integrity": "sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==",
|
||||
"version": "20.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.7.tgz",
|
||||
"integrity": "sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
@ -8538,9 +8539,9 @@
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "20.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.1.tgz",
|
||||
"integrity": "sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==",
|
||||
"version": "20.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.7.tgz",
|
||||
"integrity": "sha512-bUBrPjEry2QUTsnuEjzjbS7voGWCc30W0qzgMf90GPeDGFRakvrz47ju+oqDAKCXLUCe39u57/ORMl/O/04/9g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prop-types": {
|
||||
|
||||
@ -44,6 +44,7 @@
|
||||
"@babel/preset-env": "^7.22.7",
|
||||
"@babel/preset-typescript": "^7.22.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@types/node": "^20.4.7",
|
||||
"@types/react": "^18.2.14",
|
||||
"@types/react-dom": "^18.2.6",
|
||||
"@vitest/coverage-v8": "^0.33.0",
|
||||
|
||||
@ -7,9 +7,20 @@ import {
|
||||
} from "@rx-state/core"
|
||||
import { act, render, screen } from "@testing-library/react"
|
||||
import React, { StrictMode, useEffect, useState } from "react"
|
||||
import { defer, EMPTY, NEVER, Observable, of, startWith, Subject } from "rxjs"
|
||||
import { describe, it, expect, vi } from "vitest"
|
||||
import { bind, RemoveSubscribe, Subscribe as OriginalSubscribe } from "./"
|
||||
import { renderToPipeableStream } from "react-dom/server"
|
||||
import {
|
||||
defer,
|
||||
EMPTY,
|
||||
lastValueFrom,
|
||||
NEVER,
|
||||
Observable,
|
||||
of,
|
||||
startWith,
|
||||
Subject,
|
||||
} from "rxjs"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import { bind, Subscribe as OriginalSubscribe, RemoveSubscribe } from "./"
|
||||
import { pipeableStreamToObservable } from "./test-helpers/pipeableStreamToObservable"
|
||||
import { TestErrorBoundary } from "./test-helpers/TestErrorBoundary"
|
||||
import { useStateObservable } from "./useStateObservable"
|
||||
|
||||
@ -432,6 +443,22 @@ describe("Subscribe", () => {
|
||||
unmount()
|
||||
})
|
||||
})
|
||||
|
||||
describe("On SSR", () => {
|
||||
// Testing-library doesn't support SSR yet https://github.com/testing-library/react-testing-library/issues/561
|
||||
|
||||
it("Renders the fallback", async () => {
|
||||
const stream = renderToPipeableStream(
|
||||
<Subscribe fallback={<div>Loading</div>}>
|
||||
<div>Content</div>
|
||||
</Subscribe>,
|
||||
)
|
||||
const result = await lastValueFrom(pipeableStreamToObservable(stream))
|
||||
|
||||
expect(result).toContain("<div>Loading</div>")
|
||||
expect(result).not.toContain("<div>Content</div>")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("RemoveSubscribe", () => {
|
||||
|
||||
@ -125,6 +125,8 @@ export const Subscribe: React.FC<{
|
||||
|
||||
return fallback === undefined ? (
|
||||
actualChildren
|
||||
) : subscribedSource === null ? (
|
||||
fallback
|
||||
) : (
|
||||
<Suspense fallback={fallback}>{actualChildren}</Suspense>
|
||||
)
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
defer,
|
||||
EMPTY,
|
||||
from,
|
||||
lastValueFrom,
|
||||
merge,
|
||||
NEVER,
|
||||
Observable,
|
||||
@ -34,6 +35,8 @@ import {
|
||||
useStateObservable,
|
||||
} from "../"
|
||||
import { TestErrorBoundary } from "../test-helpers/TestErrorBoundary"
|
||||
import { renderToPipeableStream } from "react-dom/server"
|
||||
import { pipeableStreamToObservable } from "../test-helpers/pipeableStreamToObservable"
|
||||
|
||||
const wait = (ms: number) => new Promise((res) => setTimeout(res, ms))
|
||||
|
||||
@ -939,4 +942,49 @@ describe("connectObservable", () => {
|
||||
})
|
||||
expect(queryByText("Result 10")).not.toBeNull()
|
||||
})
|
||||
|
||||
describe("The hook on SSR", () => {
|
||||
// Testing-library doesn't support SSR yet https://github.com/testing-library/react-testing-library/issues/561
|
||||
|
||||
it("returns the value if the state observable has a subscription", async () => {
|
||||
const [useState, state$] = bind(of(5))
|
||||
state$.subscribe()
|
||||
const Component = () => {
|
||||
const value = useState()
|
||||
return <div>Value: {value}</div>
|
||||
}
|
||||
const stream = renderToPipeableStream(<Component />)
|
||||
const result = await lastValueFrom(pipeableStreamToObservable(stream))
|
||||
|
||||
// Sigh...
|
||||
expect(result).toEqual("<div>Value: <!-- -->5</div>")
|
||||
})
|
||||
|
||||
it("throws Missing Subscribe if the state observable doesn't have a subscription nor a default value", async () => {
|
||||
const [useState] = bind(of(5))
|
||||
const Component = () => {
|
||||
const value = useState()
|
||||
return <div>Value: {value}</div>
|
||||
}
|
||||
const stream = renderToPipeableStream(<Component />)
|
||||
try {
|
||||
await lastValueFrom(pipeableStreamToObservable(stream))
|
||||
} catch (ex: any) {
|
||||
expect(ex.message).to.equal("Missing Subscribe!")
|
||||
}
|
||||
expect.assertions(1)
|
||||
})
|
||||
|
||||
it("returns the default value if the observable didn't emit yet", async () => {
|
||||
const [useState] = bind(of(5), 3)
|
||||
const Component = () => {
|
||||
const value = useState()
|
||||
return <div>Value: {value}</div>
|
||||
}
|
||||
const stream = renderToPipeableStream(<Component />)
|
||||
const result = await lastValueFrom(pipeableStreamToObservable(stream))
|
||||
|
||||
expect(result).toEqual("<div>Value: <!-- -->3</div>")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
41
packages/core/src/test-helpers/pipeableStreamToObservable.ts
Normal file
41
packages/core/src/test-helpers/pipeableStreamToObservable.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { PipeableStream } from "react-dom/server"
|
||||
import { Observable, scan } from "rxjs"
|
||||
import { PassThrough } from "stream"
|
||||
|
||||
export function pipeableStreamToObservable(
|
||||
stream: PipeableStream,
|
||||
): Observable<string> {
|
||||
return new Observable((subscriber) => {
|
||||
const passthrough = new PassThrough()
|
||||
const sub = readStream$<string>(passthrough)
|
||||
.pipe(scan((acc, v) => acc + v, ""))
|
||||
.subscribe(subscriber)
|
||||
|
||||
stream.pipe(passthrough)
|
||||
|
||||
return () => {
|
||||
sub.unsubscribe()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function readStream$<T>(stream: NodeJS.ReadableStream) {
|
||||
return new Observable<T>((subscriber) => {
|
||||
const dataHandler = (data: T) => subscriber.next(data)
|
||||
stream.addListener("data", dataHandler)
|
||||
|
||||
const errorHandler = (error: any) => subscriber.error(error)
|
||||
stream.addListener("error", errorHandler)
|
||||
|
||||
const closeHandler = () => subscriber.complete()
|
||||
stream.addListener("close", closeHandler)
|
||||
stream.addListener("end", closeHandler)
|
||||
|
||||
return () => {
|
||||
stream.removeListener("data", dataHandler)
|
||||
stream.removeListener("error", errorHandler)
|
||||
stream.removeListener("close", closeHandler)
|
||||
stream.removeListener("end", closeHandler)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -14,7 +14,11 @@ type VoidCb = () => void
|
||||
|
||||
interface Ref<T> {
|
||||
source$: StateObservable<T>
|
||||
args: [(cb: VoidCb) => VoidCb, () => Exclude<T, SUSPENSE>]
|
||||
args: [
|
||||
(cb: VoidCb) => VoidCb,
|
||||
() => Exclude<T, typeof SUSPENSE>,
|
||||
() => Exclude<T, typeof SUSPENSE>,
|
||||
]
|
||||
}
|
||||
|
||||
export const useStateObservable = <O>(
|
||||
@ -46,7 +50,7 @@ export const useStateObservable = <O>(
|
||||
|
||||
callbackRef.current = {
|
||||
source$: null as any,
|
||||
args: [, gv] as any,
|
||||
args: [, gv, gv] as any,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user