Skip to content

Commit

Permalink
Merge pull request #74 from bcherny/personal/bcherny/async
Browse files Browse the repository at this point in the history
Add async-friendly api
  • Loading branch information
bcherny committed Dec 15, 2018
2 parents dc19b9e + a73b4b8 commit 40f0a3c
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/index.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type Options = {
export interface Store<State: Object> {
get<K: $Keys<State>>(key: K): $ElementType<State, K>,
set<K: $Keys<State>, V: $ElementType<State, K>>(key: K): (value: V) => void,
setFrom_EXPERIMENTAL(Store<State> => void): void,
on<K: $Keys<State>>(key: K): Observable<$ElementType<State, K>>,
onAll(): Observable<$Values<Undux<State>>>,
getState(): $ReadOnly<State>
Expand All @@ -29,6 +30,7 @@ export interface Store<State: Object> {
declare export class StoreSnapshot<State: Object> implements Store<State> {
get<K: $Keys<State>>(key: K): $ElementType<State, K>,
set<K: $Keys<State>, V: $ElementType<State, K>>(key: K): (value: V) => void,
setFrom_EXPERIMENTAL(Store<State> => void): void,
on<K: $Keys<State>>(key: K): Observable<$ElementType<State, K>>,
onAll(): Observable<$Values<Undux<State>>>,
getState(): $ReadOnly<State>
Expand All @@ -37,6 +39,7 @@ declare export class StoreSnapshot<State: Object> implements Store<State> {
declare export class StoreDefinition<State: Object> implements Store<State> {
get<K: $Keys<State>>(key: K): $ElementType<State, K>,
set<K: $Keys<State>, V: $ElementType<State, K>>(key: K): (value: V) => void,
setFrom_EXPERIMENTAL(Store<State> => void): void,
on<K: $Keys<State>>(key: K): Observable<$ElementType<State, K>>,
onAll(): Observable<$Values<Undux<State>>>,
getCurrentSnapshot(): StoreSnapshot<State>,
Expand Down
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type Undux<State extends object> = {
export interface Store<State extends object> {
get<K extends keyof State>(key: K): State[K]
set<K extends keyof State>(key: K): (value: State[K]) => void
setFrom_EXPERIMENTAL(f: (store: Store<State>) => void): void
on<K extends keyof State>(key: K): Observable<State[K]>
onAll(): Observable<Undux<State>[keyof State]>
getState(): Readonly<State>
Expand All @@ -42,6 +43,9 @@ export class StoreSnapshot<State extends object> implements Store<State> {
set<K extends keyof State>(key: K) {
return this.storeDefinition.set(key)
}
setFrom_EXPERIMENTAL(f: (store: Store<State>) => void): void {
return this.storeDefinition.setFrom_EXPERIMENTAL(f)
}
on<K extends keyof State>(key: K) {
return this.storeDefinition.on(key)
}
Expand Down Expand Up @@ -112,6 +116,9 @@ export class StoreDefinition<State extends object> implements Store<State> {
set<K extends keyof State>(key: K): (value: State[K]) => void {
return this.setters[key]
}
setFrom_EXPERIMENTAL(f: (store: Store<State>) => void): void {
return f(this.storeSnapshot)
}
getCurrentSnapshot() {
return this.storeSnapshot
}
Expand Down
39 changes: 39 additions & 0 deletions test/bad-cases/createConnectedStore.5.flow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// @flow
import { createConnectedStore } from '../../dist/src'
import type { Effects, Store } from '../../dist/src'
import * as React from 'react'

type State = {|
a: number,
b: number
|}

let initialState: State = {
a: 1,
b: 2
}

let withEffects: Effects<State> = store => {
store.on('a').subscribe(a => a.toUpperCase())
return store
}

let {Container, withStore} = createConnectedStore<State>(
initialState,
withEffects
)

type Props = {|
store: Store<State>,
x: boolean
|}

let A = ({store}: Props) =>
store.setFrom_EXPERIMENTAL(store => store.set('c')(2)) // Error: c is not a valid key

let B = withStore(A)

let StoreContainer = () =>
<Container>
<B x={true} />
</Container>
1 change: 1 addition & 0 deletions test/bad-cases/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
! ./node_modules/.bin/flow focus-check test/bad-cases/createConnectedStore.2.flow.js
! ./node_modules/.bin/flow focus-check test/bad-cases/createConnectedStore.3.flow.js
! ./node_modules/.bin/flow focus-check test/bad-cases/createConnectedStore.4.flow.js
! ./node_modules/.bin/flow focus-check test/bad-cases/createConnectedStore.5.flow.js
! ./node_modules/.bin/flow focus-check test/bad-cases/createConnectedStoreAs.1.flow.js
! ./node_modules/.bin/flow focus-check test/bad-cases/createConnectedStoreAs.2.flow.js
! ./node_modules/.bin/flow focus-check test/bad-cases/createConnectedStoreAs.3.flow.js
Expand Down
192 changes: 192 additions & 0 deletions test/react/createConnectedStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -698,3 +698,195 @@ test('it should return the same value when call .get multiple times for one snap
t.is(_.innerHTML, 'z')
})
})

test('it should fail for async updates by default', t => {
type State = {
as: number[]
}
let S = createConnectedStore<State>({ as: [] })
let index = 0

type Props = {
store: Store<State>
}
class A extends React.Component<Props> {
componentDidMount() {
const as = this.props.store.get('as')
this.props.store.set('as')([...as, index++])
}
render() {
return <div />
}
}
let A1 = S.withStore(A)

function B() {
return <>
<A1 />
<A1 />
<A1 />
</>
}

let store: Store<State>
let Leak = S.withStore(props => {
store = (props as any).store['storeDefinition']
return null
})

function C() {
return <S.Container>
<Leak />
<B />
</S.Container>
}
withElement(C, _ => {
t.deepEqual(store.get('as'), [2])
})
})

test('it should work for async updates using setFrom_EXPERIMENTAL', t => {
type State = {
as: number[]
}
let S = createConnectedStore<State>({ as: [] })
let index = 0

type Props = {
store: Store<State>
}
class A extends React.Component<Props> {
componentDidMount() {
this.props.store.setFrom_EXPERIMENTAL(store => {
let as = store.get('as')
store.set('as')([...as, index++])
})
}
render() {
return <div />
}
}
let A1 = S.withStore(A)

function B() {
return <>
<A1 />
<A1 />
<A1 />
</>
}

let store: Store<State>
let Leak = S.withStore(props => {
store = (props as any).store['storeDefinition']
return null
})

function C() {
return <S.Container>
<Leak />
<B />
</S.Container>
}
withElement(C, _ => {
t.deepEqual(store.get('as'), [0, 1, 2])
})
})

test('setFrom_EXPERIMENTAL should compose', t => {
type State = {
as: number[]
}
let S = createConnectedStore<State>({ as: [] })
let index = 0

type Props = {
store: Store<State>
}
class A extends React.Component<Props> {
componentDidMount() {
this.props.store.setFrom_EXPERIMENTAL(store => {
let as = store.get('as')
store.set('as')([...as, index++])

// One more time
store.setFrom_EXPERIMENTAL(store => {
let as = store.get('as')
store.set('as')([...as, index++])
})
})
}
render() {
return <div />
}
}
let A1 = S.withStore(A)

function B() {
return <>
<A1 />
<A1 />
<A1 />
</>
}

let store: Store<State>
let Leak = S.withStore(props => {
store = (props as any).store['storeDefinition']
return null
})

function C() {
return <S.Container>
<Leak />
<B />
</S.Container>
}
withElement(C, _ => {
t.deepEqual(store.get('as'), [0, 1, 2, 3, 4, 5])
})
})

test('setFrom_EXPERIMENTAL should chain', t => {
type State = {
as: number
}
let S = createConnectedStore<State>({ as: 0 })

type Props = {
store: Store<State>
}
class A extends React.Component<Props> {
componentDidMount() {
this.props.store.setFrom_EXPERIMENTAL(store =>
store.set('as')(store.get('as') + 1)
)
this.props.store.setFrom_EXPERIMENTAL(store =>
store.set('as')(store.get('as') + 1)
)
this.props.store.setFrom_EXPERIMENTAL(store =>
store.set('as')(store.get('as') + 1)
)
}
render() {
return <div />
}
}
let A1 = S.withStore(A)

let store: Store<State>
let Leak = S.withStore(props => {
store = (props as any).store['storeDefinition']
return null
})

function C() {
return <S.Container>
<Leak />
<A1 />
</S.Container>
}
withElement(C, _ => {
t.deepEqual(store.get('as'), 3)
})
})
6 changes: 6 additions & 0 deletions test/test.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ store.onAll().subscribe(_ => {
_.value === true
})

/////////////////// setFrom_EXPERIMENTAL ///////////////////

store.setFrom_EXPERIMENTAL(store => {
store.set('isTrue')(true)
})

/////////////////// connectAs ///////////////////

type CombinedA = {| a: number |}
Expand Down

0 comments on commit 40f0a3c

Please sign in to comment.