Skip to content

Commit

Permalink
remove defaultValue and add useStoreOptionally
Browse files Browse the repository at this point in the history
  • Loading branch information
awmleer committed Oct 29, 2019
1 parent 3be5777 commit 540e266
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 66 deletions.
18 changes: 0 additions & 18 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,21 +88,3 @@ This is very similar to `useMemo` and `useEffect`. But please notice that the `d
### Split Store

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.

## Default Value

You can set the `defaultValue` property on Store functions. When there is no `Provider`, `useStore` will use this `defaultValue`.

```jsx
function FooStore() {
const [x, setX] = useState(1)
return {x, setX}
}
FooStore.defaultValue = {x: 0}

function App() {
const fooStore = useStore(FooStore)
console.log(fooStore) // {x: 0}
//...
}
```
18 changes: 0 additions & 18 deletions docs/zh-cn/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,21 +85,3 @@ const fooStore = useStore(FooStore, store => [store.x > 10, store.x < 20])
### 拆分Store

我们建议对一个庞大的Store进行拆分,这样不仅代码更易于维护,性能也会有所改善。

## 默认值

你可以通过Store上的`defaultValue`属性,来设置它的默认值,在没有`Provider`时,`useStore`会使用`defaultValue`

```jsx
function FooStore() {
const [x, setX] = useState(1)
return {x, setX}
}
FooStore.defaultValue = {x: 0}

function App() {
const fooStore = useStore(FooStore)
console.log(fooStore) // {x: 0}
//...
}
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "reto",
"version": "0.7.0",
"version": "0.8.0",
"main": "index.js",
"repository": "https://github.com/awmleer/reto",
"description": "React store with hooks.",
Expand Down
16 changes: 8 additions & 8 deletions src/__tests__/__snapshots__/store.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@ exports[`Consumer 1`] = `
</DocumentFragment>
`;

exports[`default value 1`] = `
<DocumentFragment>
<div>
0
</div>
</DocumentFragment>
`;

exports[`handle return undefined from state function 1`] = `
<DocumentFragment>
<div>
Expand Down Expand Up @@ -183,3 +175,11 @@ exports[`rerender on dependency update 2`] = `
</div>
</DocumentFragment>
`;

exports[`useStoreOptionally 1`] = `
<DocumentFragment>
<div>
false
</div>
</DocumentFragment>
`;
11 changes: 4 additions & 7 deletions src/__tests__/store.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {act} from '@testing-library/react'
import {Consumer, Provider, useStore} from '..'
import {Consumer, Provider, useStore, useStoreOptionally} from '..'
import {BarStore, FooStore} from './stores/foo.store'
import * as React from 'react'
import {createRef, FC, forwardRef, memo, useImperativeHandle, useRef, useState} from 'react'
Expand Down Expand Up @@ -187,18 +187,15 @@ test('handle return undefined from state function', () => {
})


test('default value', () => {
test('useStoreOptionally', () => {
const FooStore = function() {
const [x, setX] = useState(1)
return {x, setX}
}
FooStore.defaultValue = {
x: 0
}
function App() {
const fooStore = useStore(FooStore)
const [fooStore, fooStoreExist] = useStoreOptionally(FooStore)
return (
<div>{fooStore.x}</div>
<div>{fooStoreExist.toString()}</div>
)
}
const renderer = testing.render(
Expand Down
30 changes: 22 additions & 8 deletions src/consumer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import * as React from 'react'
import {ReactNode, useContext, useDebugValue, useEffect, useRef, useState} from 'react'
import {ReactElement} from 'react'
import {Container} from './container'
import {getStoreContext, Store, StoreV} from './store'
import {NoStoreError} from './error'
import {defaultStoreValue, getStoreContext, Store, StoreV} from './store'

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

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

export function useStore<S extends Store>(S: S, deps?: Deps<StoreV<S>>) {
const name = S.displayName || S.name
export function useStore<S extends Store>(s: S, deps?: Deps<StoreV<S>>) {
const name = s.displayName || s.name
useDebugValue(name)
const hasDefaultValue = S.hasOwnProperty('defaultValue')
const Context = hasDefaultValue ? getStoreContext(S) : S.Context
if (!Context) {
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.`)
}
const Context = getStoreContext(s)
const container = useContext(Context) as Container<StoreV<S>>

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

if (state === defaultStoreValue) {
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)
}

return state
}

export function useStoreOptionally<S extends Store>(s: S, deps?: Deps<StoreV<S>>): [StoreV<S> | undefined, boolean] {
try {
const store = useStore(s, deps)
return [store, true]
} catch (e) {
if (e instanceof NoStoreError) {
return [undefined, false]
} else {
throw e
}
}
}

function compare(oldDeps: unknown[], newDeps: unknown[]) {
if (oldDeps.length !== newDeps.length) {
return true
Expand Down
8 changes: 8 additions & 0 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {Store} from './store'

export class NoStoreError<S extends Store> {
constructor(
public message: string,
public store: S,
) {}
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export {useStore, Consumer} from './consumer'
export {useStore, useStoreOptionally, Consumer} from './consumer'
export {Provider, Props} from './provider'
export {Store} from './store'
10 changes: 5 additions & 5 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {Container} from './container'

export interface Store<P extends unknown[] = unknown[], V = unknown> {
(...args: P): V
defaultValue?: V
displayName?: string
// defaultProps?

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

export function getStoreContext<S extends Store>(S: S) {
if (!S.Context) {
S.Context = React.createContext(new Container(S.defaultValue))
export const defaultStoreValue = Symbol() as any
export function getStoreContext<S extends Store>(s: S) {
if (!s.Context) {
s.Context = React.createContext(new Container(defaultStoreValue))
}
return S.Context
return s.Context
}

0 comments on commit 540e266

Please sign in to comment.