feat(core/useObservable): switch state when changing source

This commit is contained in:
Víctor Oliva 2020-11-10 23:54:39 +01:00 committed by Josep M Sobrepere
parent 90e6a00920
commit a37c1ca8db
2 changed files with 43 additions and 5 deletions

View File

@ -17,6 +17,7 @@ import {
fireEvent,
screen,
render,
act,
} from "@testing-library/react"
import { bind, Subscribe } from "../"
import { TestErrorBoundary } from "../test-helpers/TestErrorBoundary"
@ -183,6 +184,30 @@ describe("connectFactoryObservable", () => {
subs.unsubscribe()
})
it("immediately switches the state to the new observable", () => {
const [useNumber, getNumber$] = bind((x: number) => of(x))
const subs = merge(
getNumber$(0),
getNumber$(1),
getNumber$(2),
).subscribe()
const Form = ({ id }: { id: number }) => {
const value = useNumber(id)
return <input role="input" key={id} defaultValue={value} />
}
const { rerender, getByRole } = render(<Form id={0} />)
expect((getByRole("input") as HTMLInputElement).value).toBe("0")
act(() => rerender(<Form id={1} />))
expect((getByRole("input") as HTMLInputElement).value).toBe("1")
act(() => rerender(<Form id={2} />))
expect((getByRole("input") as HTMLInputElement).value).toBe("2")
})
it("handles optional args correctly", () => {
const [, getNumber$] = bind((x: number, y?: number) => of(x + (y ?? 0)))

View File

@ -8,8 +8,15 @@ export const useObservable = <O>(
keys: Array<any>,
defaultValue: O,
): Exclude<O, typeof SUSPENSE> => {
const [state, setState] = useState(source$.gV)
const prevStateRef = useRef<O | (() => O)>(state)
const [state, setState] = useState<[O, any[]]>(() => [source$.gV(), keys])
const prevStateRef = useRef<O | (() => O)>(state[0])
if (
keys.length !== state[1].length ||
keys.some((k, i) => state[1][i] !== k)
) {
setState([source$.gV(), keys])
}
useEffect(() => {
const { gV } = source$
@ -29,8 +36,14 @@ export const useObservable = <O>(
if (err !== EMPTY_VALUE) return
const set = (value: O | (() => O)) => {
if (!Object.is(prevStateRef.current, value))
setState((prevStateRef.current = value))
if (!Object.is(prevStateRef.current, value)) {
prevStateRef.current = value
if (typeof value === "function") {
setState(() => [(value as any)(), keys])
} else {
setState([value, keys])
}
}
}
if (syncVal === EMPTY_VALUE) {
@ -48,5 +61,5 @@ export const useObservable = <O>(
}
}, keys)
return state as Exclude<O, typeof SUSPENSE>
return state[0] as Exclude<O, typeof SUSPENSE>
}