Skip to content

Commit 7a3c026

Browse files
volivajosepot
andcommitted
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>
1 parent 39ba60b commit 7a3c026

File tree

5 files changed

+93
-5
lines changed

5 files changed

+93
-5
lines changed

packages/core/src/bind/connectFactoryObservable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Observable } from "rxjs"
22
import { EMPTY_VALUE } from "../internal/empty-value"
33
import { state, StateObservable, SUSPENSE } from "@rx-state/core"
4-
import { useStateObservable } from "../useStateObservable"
4+
import { useStateObservable } from "../"
55

66
/**
77
* Accepts: A factory function that returns an Observable.

packages/core/src/bind/connectObservable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { EMPTY_VALUE } from "../internal/empty-value"
22
import { Observable } from "rxjs"
3-
import { useStateObservable } from "../useStateObservable"
3+
import { useStateObservable } from "../"
44
import { state } from "@rx-state/core"
55

66
/**

packages/core/src/index.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1-
export * from "@rx-state/core"
1+
export {
2+
AddStopArg,
3+
DefaultedStateObservable,
4+
EmptyObservableError,
5+
liftSuspense,
6+
NoSubscribersError,
7+
PipeState,
8+
sinkSuspense,
9+
StateObservable,
10+
StatePromise,
11+
SUSPENSE,
12+
withDefault,
13+
WithDefaultOperator,
14+
} from "@rx-state/core"
15+
export { bind } from "./bind"
216
export { shareLatest } from "./shareLatest"
17+
export { state } from "./stateJsx"
18+
export { RemoveSubscribe, Subscribe } from "./Subscribe"
319
export { useStateObservable } from "./useStateObservable"
4-
export { bind } from "./bind"
5-
export { Subscribe, RemoveSubscribe } from "./Subscribe"

packages/core/src/stateJsx.test.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { render, screen, act } from "@testing-library/react"
2+
import React, { Suspense } from "react"
3+
import { map, Subject } from "rxjs"
4+
import { state } from "./"
5+
6+
describe("stateJsx", () => {
7+
it("is possible to use StateObservables as JSX elements", async () => {
8+
const subject = new Subject<string>()
9+
const state$ = state(subject)
10+
const subscription = state$.subscribe()
11+
12+
render(<Suspense fallback="Waiting">{state$}</Suspense>)
13+
14+
expect(screen.queryByText("Result")).toBeNull()
15+
expect(screen.queryByText("Waiting")).not.toBeNull()
16+
17+
await act(() => {
18+
subject.next("Result")
19+
return Promise.resolve()
20+
})
21+
22+
expect(screen.queryByText("Result")).not.toBeNull()
23+
expect(screen.queryByText("Waiting")).toBeNull()
24+
subscription.unsubscribe()
25+
})
26+
27+
it("is possible to use factory StateObservables as JSX elements", async () => {
28+
const subject = new Subject<string>()
29+
const state$ = state((value: string) => subject.pipe(map((x) => value + x)))
30+
31+
const subscription = state$("hello ").subscribe()
32+
33+
render(<Suspense fallback="Waiting">{state$("hello ")}</Suspense>)
34+
35+
expect(screen.queryByText("hello world!")).toBeNull()
36+
expect(screen.queryByText("Waiting")).not.toBeNull()
37+
38+
await act(() => {
39+
subject.next("world!")
40+
return Promise.resolve()
41+
})
42+
43+
expect(screen.queryByText("hello world!")).not.toBeNull()
44+
expect(screen.queryByText("Waiting")).toBeNull()
45+
subscription.unsubscribe()
46+
})
47+
})

packages/core/src/stateJsx.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { state as coreState, StateObservable } from "@rx-state/core"
2+
import React, { createElement, ReactElement } from "react"
3+
import { useStateObservable } from "./useStateObservable"
4+
5+
declare module "@rx-state/core" {
6+
interface StateObservable<T> extends ReactElement {}
7+
}
8+
9+
export const state: typeof coreState = (...args: any[]): any => {
10+
const result = (coreState as any)(...args)
11+
12+
if (typeof result === "function") {
13+
return (...args: any[]) => enhanceState(result(...args))
14+
}
15+
return enhanceState(result)
16+
}
17+
18+
const cache = new WeakMap<StateObservable<any>, React.ReactNode>()
19+
function enhanceState<T>(state$: StateObservable<T>) {
20+
if (!cache.has(state$))
21+
cache.set(
22+
state$,
23+
createElement(() => useStateObservable(state$) as any, {}),
24+
)
25+
26+
return Object.assign(state$, cache.get(state$)!)
27+
}

0 commit comments

Comments
 (0)