Skip to content

Commit a37c1ca

Browse files
volivajosepot
authored andcommitted
feat(core/useObservable): switch state when changing source
1 parent 90e6a00 commit a37c1ca

File tree

2 files changed

+43
-5
lines changed

2 files changed

+43
-5
lines changed

packages/core/src/bind/connectFactoryObservable.test.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
fireEvent,
1818
screen,
1919
render,
20+
act,
2021
} from "@testing-library/react"
2122
import { bind, Subscribe } from "../"
2223
import { TestErrorBoundary } from "../test-helpers/TestErrorBoundary"
@@ -183,6 +184,30 @@ describe("connectFactoryObservable", () => {
183184
subs.unsubscribe()
184185
})
185186

187+
it("immediately switches the state to the new observable", () => {
188+
const [useNumber, getNumber$] = bind((x: number) => of(x))
189+
const subs = merge(
190+
getNumber$(0),
191+
getNumber$(1),
192+
getNumber$(2),
193+
).subscribe()
194+
195+
const Form = ({ id }: { id: number }) => {
196+
const value = useNumber(id)
197+
198+
return <input role="input" key={id} defaultValue={value} />
199+
}
200+
201+
const { rerender, getByRole } = render(<Form id={0} />)
202+
expect((getByRole("input") as HTMLInputElement).value).toBe("0")
203+
204+
act(() => rerender(<Form id={1} />))
205+
expect((getByRole("input") as HTMLInputElement).value).toBe("1")
206+
207+
act(() => rerender(<Form id={2} />))
208+
expect((getByRole("input") as HTMLInputElement).value).toBe("2")
209+
})
210+
186211
it("handles optional args correctly", () => {
187212
const [, getNumber$] = bind((x: number, y?: number) => of(x + (y ?? 0)))
188213

packages/core/src/internal/useObservable.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,15 @@ export const useObservable = <O>(
88
keys: Array<any>,
99
defaultValue: O,
1010
): Exclude<O, typeof SUSPENSE> => {
11-
const [state, setState] = useState(source$.gV)
12-
const prevStateRef = useRef<O | (() => O)>(state)
11+
const [state, setState] = useState<[O, any[]]>(() => [source$.gV(), keys])
12+
const prevStateRef = useRef<O | (() => O)>(state[0])
13+
14+
if (
15+
keys.length !== state[1].length ||
16+
keys.some((k, i) => state[1][i] !== k)
17+
) {
18+
setState([source$.gV(), keys])
19+
}
1320

1421
useEffect(() => {
1522
const { gV } = source$
@@ -29,8 +36,14 @@ export const useObservable = <O>(
2936
if (err !== EMPTY_VALUE) return
3037

3138
const set = (value: O | (() => O)) => {
32-
if (!Object.is(prevStateRef.current, value))
33-
setState((prevStateRef.current = value))
39+
if (!Object.is(prevStateRef.current, value)) {
40+
prevStateRef.current = value
41+
if (typeof value === "function") {
42+
setState(() => [(value as any)(), keys])
43+
} else {
44+
setState([value, keys])
45+
}
46+
}
3447
}
3548

3649
if (syncVal === EMPTY_VALUE) {
@@ -48,5 +61,5 @@ export const useObservable = <O>(
4861
}
4962
}, keys)
5063

51-
return state as Exclude<O, typeof SUSPENSE>
64+
return state[0] as Exclude<O, typeof SUSPENSE>
5265
}

0 commit comments

Comments
 (0)