Skip to content

Commit 1fb2317

Browse files
committed
fix(hooks): first time rendering behavior issue
fix #29
1 parent c95f434 commit 1fb2317

File tree

3 files changed

+70
-12
lines changed

3 files changed

+70
-12
lines changed

src/hooks/use-ayanami-instance.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as React from 'react'
22
import { get } from 'lodash'
33

44
import { ActionMethodOfAyanami, Ayanami, combineWithIkari } from '../core'
5+
import { useSubscribeAyanamiState } from './use-subscribe-ayanami-state'
56

67
export interface UseAyanamiInstanceConfig {
78
destroyWhenUnmount?: boolean
@@ -20,19 +21,8 @@ export function useAyanamiInstance<M extends Ayanami<S>, S>(
2021
ayanami: M,
2122
config?: Config,
2223
): Result<M, S> {
23-
const ayanamiRef = React.useRef(ayanami)
2424
const ikari = React.useMemo(() => combineWithIkari(ayanami), [ayanami])
25-
const [state, setState] = React.useState<S>(() => ayanami.getState())
26-
27-
if (ayanamiRef.current !== ayanami) {
28-
ayanamiRef.current = ayanami
29-
setState(ayanami.getState())
30-
}
31-
32-
React.useEffect(() => {
33-
const subscription = ayanami.getState$().subscribe(setState)
34-
return () => subscription.unsubscribe()
35-
}, [ayanami])
25+
const state = useSubscribeAyanamiState(ayanami)
3626

3727
React.useEffect(
3828
() => () => {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import * as React from 'react'
2+
import { Subscription } from 'rxjs'
3+
4+
import { Ayanami } from '../core'
5+
6+
export function useSubscribeAyanamiState<M extends Ayanami<S>, S>(ayanami: M): S {
7+
const ayanamiRef = React.useRef<Ayanami<S> | null>(null)
8+
const subscriptionRef = React.useRef<Subscription | null>(null)
9+
10+
const [state, setState] = React.useState<S>(() => ayanami.getState())
11+
12+
if (ayanamiRef.current !== ayanami) {
13+
ayanamiRef.current = ayanami
14+
15+
if (subscriptionRef.current) {
16+
subscriptionRef.current.unsubscribe()
17+
subscriptionRef.current = null
18+
}
19+
20+
if (ayanami) {
21+
subscriptionRef.current = ayanami.getState$().subscribe(setState)
22+
}
23+
}
24+
25+
React.useEffect(
26+
() => () => {
27+
if (subscriptionRef.current) {
28+
subscriptionRef.current.unsubscribe()
29+
}
30+
},
31+
[subscriptionRef],
32+
)
33+
34+
return state
35+
}

test/specs/hooks.spec.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Observable } from 'rxjs'
55
import { map, withLatestFrom } from 'rxjs/operators'
66

77
import { Ayanami, Effect, EffectAction, Reducer, useAyanami, TransientScope } from '../../src'
8+
import { useCallback, useEffect } from 'react'
89

910
interface State {
1011
count: number
@@ -84,6 +85,38 @@ describe('Hooks spec:', () => {
8485
click(CountAction.MINUS)
8586
expect(count()).toBe('0')
8687
})
88+
89+
it('should only render once when update the state right during rendering', () => {
90+
const spy = jest.fn()
91+
const TestComponent = () => {
92+
const [state, actions] = useAyanami(Count, { scope: TransientScope })
93+
const addOne = useCallback(() => actions.add(1), [])
94+
95+
if (state.count % 2 === 0) {
96+
actions.add(1)
97+
}
98+
99+
useEffect(() => {
100+
spy(state.count)
101+
}, [state.count])
102+
103+
return (
104+
<div>
105+
<p>count: {state.count}</p>
106+
<button onClick={addOne}>add one</button>
107+
</div>
108+
)
109+
}
110+
111+
const renderer = create(<TestComponent />)
112+
113+
// https://github.com/facebook/react/issues/14050 to trigger useEffect manually
114+
renderer.update(<TestComponent />)
115+
expect(spy.mock.calls).toEqual([[1]])
116+
117+
act(() => renderer.root.findByType('button').props.onClick())
118+
expect(spy.mock.calls).toEqual([[1], [3]])
119+
})
87120
})
88121

89122
describe('Scope behavior', () => {

0 commit comments

Comments
 (0)