Skip to content

Commit

Permalink
fix(observable-hooks): get initial value from BehaviorSubject
Browse files Browse the repository at this point in the history
fix #88
  • Loading branch information
crimx committed Nov 10, 2021
1 parent a8bd0ea commit 7603979
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,37 @@ describe('useLayoutObservableState', () => {

it('should get the init state of BehaviorSubject without initialState', () => {
const value$ = new BehaviorSubject('22')
const { result } = renderHook(() => useLayoutObservableState(value$))
const renderTimes = jest.fn()
const { result } = renderHook(() => {
const result = useLayoutObservableState(value$)
renderTimes(result)
return result
})
expect(result.current).toBe('22')
expect(renderTimes).toHaveBeenCalledTimes(1)
expect(renderTimes).toHaveBeenLastCalledWith('22')
})

it('should ignore the manual initialState for BehaviorSubject', () => {
const value$ = new BehaviorSubject('22')
const { result } = renderHook(() =>
useLayoutObservableState(value$, 'initialState')
)
const renderTimes = jest.fn()
const { result } = renderHook(() => {
const result = useLayoutObservableState(value$, 'initialState')
renderTimes(result)
return result
})
expect(result.current).toBe('22')
expect(renderTimes).toBeCalledTimes(1)
expect(renderTimes).toHaveBeenLastCalledWith('22')

act(() => value$.next('22'))
expect(result.current).toBe('22')
expect(renderTimes).toBeCalledTimes(1)

act(() => value$.next('33'))
expect(result.current).toBe('33')
expect(renderTimes).toBeCalledTimes(2)
expect(renderTimes).toHaveBeenLastCalledWith('33')
})

it('should ignore the given init state when Observable also emits sync values', () => {
Expand Down
29 changes: 25 additions & 4 deletions packages/observable-hooks/__tests__/use-observable-state.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,37 @@ describe('useObservableState', () => {

it('should get the init state of BehaviorSubject without initialState', () => {
const value$ = new BehaviorSubject('22')
const { result } = renderHook(() => useObservableState(value$))
const renderTimes = jest.fn()
const { result } = renderHook(() => {
const result = useObservableState(value$)
renderTimes(result)
return result
})
expect(result.current).toBe('22')
expect(renderTimes).toHaveBeenCalledTimes(1)
expect(renderTimes).toHaveBeenLastCalledWith('22')
})

it('should ignore the manual initialState for BehaviorSubject', () => {
const value$ = new BehaviorSubject('22')
const { result } = renderHook(() =>
useObservableState(value$, 'initialState')
)
const renderTimes = jest.fn()
const { result } = renderHook(() => {
const result = useObservableState(value$, 'initialState')
renderTimes(result)
return result
})
expect(result.current).toBe('22')
expect(renderTimes).toBeCalledTimes(1)
expect(renderTimes).toHaveBeenLastCalledWith('22')

act(() => value$.next('22'))
expect(result.current).toBe('22')
expect(renderTimes).toBeCalledTimes(1)

act(() => value$.next('33'))
expect(result.current).toBe('33')
expect(renderTimes).toBeCalledTimes(2)
expect(renderTimes).toHaveBeenLastCalledWith('33')
})

it('should ignore the given init state when Observable also emits sync values', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Observable, isObservable, Subject } from 'rxjs'
import { Observable, isObservable, Subject, BehaviorSubject } from 'rxjs'
import { useState, useRef, useDebugValue } from 'react'
import type { useSubscription as useSubscriptionType } from '../use-subscription'
import { useRefFn, getEmptySubject } from '../helpers'
Expand All @@ -13,29 +13,45 @@ export function useObservableStateInternal<TState, TInput = TState>(
) => Observable<TState>),
initialState?: TState | (() => TState)
): TState | undefined | [TState | undefined, (input: TInput) => void] {
const [state, setState] = useState<TState | undefined>(initialState)
// Even though hooks are under conditional block
// it is for a completely different use case
// which unlikely coexists with the other one.
// A warning is also added to the docs.
if (isObservable(state$OrInit)) {
const state$ = state$OrInit
const [state, setState] = useState<TState | undefined>(() => {
if (
state$ instanceof BehaviorSubject ||
(state$ as BehaviorSubject<TState>).value !== undefined
) {
return (state$ as BehaviorSubject<TState>).value
}
if (typeof initialState === 'function') {
return (initialState as () => TState)()
}
return initialState
})

let callback: undefined | ((input: TInput) => void)
let state$: Observable<TState>
useSubscription(state$, setState)

if (isObservable(state$OrInit)) {
state$ = state$OrInit
useDebugValue(state)

return state
} else {
const init = state$OrInit
// Even though hooks are under conditional block
// it is for a completely different use case
// which unlikely coexists with the other one.
// A warning is also added to the docs.
const [state, setState] = useState<TState | undefined>(initialState)

const input$Ref = useRefFn<Subject<TInput>>(getEmptySubject)

state$ = useRefFn(() => init(input$Ref.current, state)).current
callback = useRef((state: TInput) => input$Ref.current.next(state)).current
}
const state$ = useRefFn(() => init(input$Ref.current, state)).current
const callback = useRef((state: TInput) =>
input$Ref.current.next(state)
).current

useSubscription(state$, setState)
useSubscription(state$, setState)

// Display state in React DevTools.
useDebugValue(state)
useDebugValue(state)

return callback ? [state, callback] : state
return [state, callback]
}
}

0 comments on commit 7603979

Please sign in to comment.