feat: use StateObservables as React elements (#280)

* override all

* simplifyTypings

* feat: improvements and tests to `stateJsx`

Co-authored-by: Josep M Sobrepere <jm.sobrepere@gmail.com>
This commit is contained in:
Victor Oliva 2022-09-09 12:18:06 +02:00 committed by GitHub
parent 0c90ab717b
commit 59a8a2a5e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 6 deletions

View File

@ -1,7 +1,7 @@
import { Observable } from "rxjs"
import { EMPTY_VALUE } from "../internal/empty-value"
import { state, StateObservable, SUSPENSE } from "@rx-state/core"
import { useStateObservable } from "../useStateObservable"
import { useStateObservable } from "../"
/**
* Accepts: A factory function that returns an Observable.

View File

@ -1,6 +1,6 @@
import { EMPTY_VALUE } from "../internal/empty-value"
import { Observable } from "rxjs"
import { useStateObservable } from "../useStateObservable"
import { useStateObservable } from "../"
import { state } from "@rx-state/core"
/**

View File

@ -1,5 +1,19 @@
export * from "@rx-state/core"
export { shareLatest } from "./shareLatest"
export { useStateObservable } from "./useStateObservable"
export {
AddStopArg,
DefaultedStateObservable,
EmptyObservableError,
liftSuspense,
NoSubscribersError,
PipeState,
sinkSuspense,
StateObservable,
StatePromise,
SUSPENSE,
withDefault,
WithDefaultOperator,
} from "@rx-state/core"
export { bind } from "./bind"
export { Subscribe, RemoveSubscribe } from "./Subscribe"
export { shareLatest } from "./shareLatest"
export { state } from "./stateJsx"
export { RemoveSubscribe, Subscribe } from "./Subscribe"
export { useStateObservable } from "./useStateObservable"

View File

@ -0,0 +1,47 @@
import { render, screen, act } from "@testing-library/react"
import React, { Suspense } from "react"
import { map, Subject } from "rxjs"
import { state } from "./"
describe("stateJsx", () => {
it("is possible to use StateObservables as JSX elements", async () => {
const subject = new Subject<string>()
const state$ = state(subject)
const subscription = state$.subscribe()
render(<Suspense fallback="Waiting">{state$}</Suspense>)
expect(screen.queryByText("Result")).toBeNull()
expect(screen.queryByText("Waiting")).not.toBeNull()
await act(() => {
subject.next("Result")
return Promise.resolve()
})
expect(screen.queryByText("Result")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
subscription.unsubscribe()
})
it("is possible to use factory StateObservables as JSX elements", async () => {
const subject = new Subject<string>()
const state$ = state((value: string) => subject.pipe(map((x) => value + x)))
const subscription = state$("hello ").subscribe()
render(<Suspense fallback="Waiting">{state$("hello ")}</Suspense>)
expect(screen.queryByText("hello world!")).toBeNull()
expect(screen.queryByText("Waiting")).not.toBeNull()
await act(() => {
subject.next("world!")
return Promise.resolve()
})
expect(screen.queryByText("hello world!")).not.toBeNull()
expect(screen.queryByText("Waiting")).toBeNull()
subscription.unsubscribe()
})
})

View File

@ -0,0 +1,27 @@
import { state as coreState, StateObservable } from "@rx-state/core"
import React, { createElement, ReactElement } from "react"
import { useStateObservable } from "./useStateObservable"
declare module "@rx-state/core" {
interface StateObservable<T> extends ReactElement {}
}
export const state: typeof coreState = (...args: any[]): any => {
const result = (coreState as any)(...args)
if (typeof result === "function") {
return (...args: any[]) => enhanceState(result(...args))
}
return enhanceState(result)
}
const cache = new WeakMap<StateObservable<any>, React.ReactNode>()
function enhanceState<T>(state$: StateObservable<T>) {
if (!cache.has(state$))
cache.set(
state$,
createElement(() => useStateObservable(state$) as any, {}),
)
return Object.assign(state$, cache.get(state$)!)
}