Skip to content

Commit 540e266

Browse files
committed
remove defaultValue and add useStoreOptionally
1 parent 3be5777 commit 540e266

File tree

9 files changed

+49
-66
lines changed

9 files changed

+49
-66
lines changed

docs/advanced.md

-18
Original file line numberDiff line numberDiff line change
@@ -88,21 +88,3 @@ This is very similar to `useMemo` and `useEffect`. But please notice that the `d
8888
### Split Store
8989

9090
We recommend splitting a large Store into small parts, so that not only is the code easier to maintain, but performance can also get improved.
91-
92-
## Default Value
93-
94-
You can set the `defaultValue` property on Store functions. When there is no `Provider`, `useStore` will use this `defaultValue`.
95-
96-
```jsx
97-
function FooStore() {
98-
const [x, setX] = useState(1)
99-
return {x, setX}
100-
}
101-
FooStore.defaultValue = {x: 0}
102-
103-
function App() {
104-
const fooStore = useStore(FooStore)
105-
console.log(fooStore) // {x: 0}
106-
//...
107-
}
108-
```

docs/zh-cn/advanced.md

-18
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,3 @@ const fooStore = useStore(FooStore, store => [store.x > 10, store.x < 20])
8585
### 拆分Store
8686

8787
我们建议对一个庞大的Store进行拆分,这样不仅代码更易于维护,性能也会有所改善。
88-
89-
## 默认值
90-
91-
你可以通过Store上的`defaultValue`属性,来设置它的默认值,在没有`Provider`时,`useStore`会使用`defaultValue`
92-
93-
```jsx
94-
function FooStore() {
95-
const [x, setX] = useState(1)
96-
return {x, setX}
97-
}
98-
FooStore.defaultValue = {x: 0}
99-
100-
function App() {
101-
const fooStore = useStore(FooStore)
102-
console.log(fooStore) // {x: 0}
103-
//...
104-
}
105-
```

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "reto",
3-
"version": "0.7.0",
3+
"version": "0.8.0",
44
"main": "index.js",
55
"repository": "https://github.com/awmleer/reto",
66
"description": "React store with hooks.",

src/__tests__/__snapshots__/store.test.tsx.snap

+8-8
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,6 @@ exports[`Consumer 1`] = `
66
</DocumentFragment>
77
`;
88

9-
exports[`default value 1`] = `
10-
<DocumentFragment>
11-
<div>
12-
0
13-
</div>
14-
</DocumentFragment>
15-
`;
16-
179
exports[`handle return undefined from state function 1`] = `
1810
<DocumentFragment>
1911
<div>
@@ -183,3 +175,11 @@ exports[`rerender on dependency update 2`] = `
183175
</div>
184176
</DocumentFragment>
185177
`;
178+
179+
exports[`useStoreOptionally 1`] = `
180+
<DocumentFragment>
181+
<div>
182+
false
183+
</div>
184+
</DocumentFragment>
185+
`;

src/__tests__/store.test.tsx

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {act} from '@testing-library/react'
2-
import {Consumer, Provider, useStore} from '..'
2+
import {Consumer, Provider, useStore, useStoreOptionally} from '..'
33
import {BarStore, FooStore} from './stores/foo.store'
44
import * as React from 'react'
55
import {createRef, FC, forwardRef, memo, useImperativeHandle, useRef, useState} from 'react'
@@ -187,18 +187,15 @@ test('handle return undefined from state function', () => {
187187
})
188188

189189

190-
test('default value', () => {
190+
test('useStoreOptionally', () => {
191191
const FooStore = function() {
192192
const [x, setX] = useState(1)
193193
return {x, setX}
194194
}
195-
FooStore.defaultValue = {
196-
x: 0
197-
}
198195
function App() {
199-
const fooStore = useStore(FooStore)
196+
const [fooStore, fooStoreExist] = useStoreOptionally(FooStore)
200197
return (
201-
<div>{fooStore.x}</div>
198+
<div>{fooStoreExist.toString()}</div>
202199
)
203200
}
204201
const renderer = testing.render(

src/consumer.tsx

+22-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import * as React from 'react'
22
import {ReactNode, useContext, useDebugValue, useEffect, useRef, useState} from 'react'
33
import {ReactElement} from 'react'
44
import {Container} from './container'
5-
import {getStoreContext, Store, StoreV} from './store'
5+
import {NoStoreError} from './error'
6+
import {defaultStoreValue, getStoreContext, Store, StoreV} from './store'
67

78
type Deps<T> = (store: T) => unknown[]
89

@@ -17,14 +18,10 @@ export function Consumer<S extends Store>(props: Props<S>) {
1718
return props.children(store) as ReactElement
1819
}
1920

20-
export function useStore<S extends Store>(S: S, deps?: Deps<StoreV<S>>) {
21-
const name = S.displayName || S.name
21+
export function useStore<S extends Store>(s: S, deps?: Deps<StoreV<S>>) {
22+
const name = s.displayName || s.name
2223
useDebugValue(name)
23-
const hasDefaultValue = S.hasOwnProperty('defaultValue')
24-
const Context = hasDefaultValue ? getStoreContext(S) : S.Context
25-
if (!Context) {
26-
throw new Error(`No store context of "${name}" found. And "${name}" doesn't have defaultValue. Either render a Provider or set a defaultValue for this Store.`)
27-
}
24+
const Context = getStoreContext(s)
2825
const container = useContext(Context) as Container<StoreV<S>>
2926

3027
const [state, setState] = useState<StoreV<S>>(container.state)
@@ -50,9 +47,26 @@ export function useStore<S extends Store>(S: S, deps?: Deps<StoreV<S>>) {
5047
}
5148
}, [])
5249

50+
if (state === defaultStoreValue) {
51+
throw new NoStoreError(`No store context of "${name}" found. And "${name}" doesn't have defaultValue. Either render a Provider or set a defaultValue for this Store.`, s)
52+
}
53+
5354
return state
5455
}
5556

57+
export function useStoreOptionally<S extends Store>(s: S, deps?: Deps<StoreV<S>>): [StoreV<S> | undefined, boolean] {
58+
try {
59+
const store = useStore(s, deps)
60+
return [store, true]
61+
} catch (e) {
62+
if (e instanceof NoStoreError) {
63+
return [undefined, false]
64+
} else {
65+
throw e
66+
}
67+
}
68+
}
69+
5670
function compare(oldDeps: unknown[], newDeps: unknown[]) {
5771
if (oldDeps.length !== newDeps.length) {
5872
return true

src/error.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {Store} from './store'
2+
3+
export class NoStoreError<S extends Store> {
4+
constructor(
5+
public message: string,
6+
public store: S,
7+
) {}
8+
}

src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export {useStore, Consumer} from './consumer'
1+
export {useStore, useStoreOptionally, Consumer} from './consumer'
22
export {Provider, Props} from './provider'
33
export {Store} from './store'

src/store.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {Container} from './container'
33

44
export interface Store<P extends unknown[] = unknown[], V = unknown> {
55
(...args: P): V
6-
defaultValue?: V
76
displayName?: string
87
// defaultProps?
98

@@ -13,9 +12,10 @@ export interface Store<P extends unknown[] = unknown[], V = unknown> {
1312
export type StoreP<S extends Store> = S extends Store<infer P, any> ? P : never
1413
export type StoreV<S extends Store> = S extends Store<any, infer V> ? V : never
1514

16-
export function getStoreContext<S extends Store>(S: S) {
17-
if (!S.Context) {
18-
S.Context = React.createContext(new Container(S.defaultValue))
15+
export const defaultStoreValue = Symbol() as any
16+
export function getStoreContext<S extends Store>(s: S) {
17+
if (!s.Context) {
18+
s.Context = React.createContext(new Container(defaultStoreValue))
1919
}
20-
return S.Context
20+
return s.Context
2121
}

0 commit comments

Comments
 (0)