Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/framework/react/reference/functions/shallow.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ title: shallow
function shallow<T>(objA, objB): boolean
```

Defined in: [index.ts:34](https://github.com/TanStack/store/blob/main/packages/react-store/src/index.ts#L34)
Defined in: [index.ts:42](https://github.com/TanStack/store/blob/main/packages/react-store/src/index.ts#L42)

## Type Parameters

Expand Down
22 changes: 18 additions & 4 deletions docs/framework/react/reference/functions/usestore.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ title: useStore
## Call Signature

```ts
function useStore<TState, TSelected>(store, selector?): TSelected
function useStore<TState, TSelected>(
store,
selector?,
options?): TSelected
```

Defined in: [index.ts:11](https://github.com/TanStack/store/blob/main/packages/react-store/src/index.ts#L11)
Defined in: [index.ts:15](https://github.com/TanStack/store/blob/main/packages/react-store/src/index.ts#L15)

### Type Parameters

Expand All @@ -31,17 +34,24 @@ Defined in: [index.ts:11](https://github.com/TanStack/store/blob/main/packages/r

(`state`) => `TSelected`

#### options?

`UseStoreOptions`\<`TSelected`\>

### Returns

`TSelected`

## Call Signature

```ts
function useStore<TState, TSelected>(store, selector?): TSelected
function useStore<TState, TSelected>(
store,
selector?,
options?): TSelected
```

Defined in: [index.ts:15](https://github.com/TanStack/store/blob/main/packages/react-store/src/index.ts#L15)
Defined in: [index.ts:20](https://github.com/TanStack/store/blob/main/packages/react-store/src/index.ts#L20)

### Type Parameters

Expand All @@ -59,6 +69,10 @@ Defined in: [index.ts:15](https://github.com/TanStack/store/blob/main/packages/r

(`state`) => `TSelected`

#### options?

`UseStoreOptions`\<`TSelected`\>

### Returns

`TSelected`
2 changes: 1 addition & 1 deletion docs/framework/solid/reference/functions/shallow.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ title: shallow
function shallow<T>(objA, objB): boolean
```

Defined in: [index.tsx:41](https://github.com/TanStack/store/blob/main/packages/solid-store/src/index.tsx#L41)
Defined in: [index.tsx:49](https://github.com/TanStack/store/blob/main/packages/solid-store/src/index.tsx#L49)

## Type Parameters

Expand Down
22 changes: 18 additions & 4 deletions docs/framework/solid/reference/functions/usestore.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ title: useStore
## Call Signature

```ts
function useStore<TState, TSelected>(store, selector?): Accessor<TSelected>
function useStore<TState, TSelected>(
store,
selector?,
options?): Accessor<TSelected>
```

Defined in: [index.tsx:12](https://github.com/TanStack/store/blob/main/packages/solid-store/src/index.tsx#L12)
Defined in: [index.tsx:16](https://github.com/TanStack/store/blob/main/packages/solid-store/src/index.tsx#L16)

### Type Parameters

Expand All @@ -31,17 +34,24 @@ Defined in: [index.tsx:12](https://github.com/TanStack/store/blob/main/packages/

(`state`) => `TSelected`

#### options?

`UseStoreOptions`\<`TSelected`\>

### Returns

`Accessor`\<`TSelected`\>

## Call Signature

```ts
function useStore<TState, TSelected>(store, selector?): Accessor<TSelected>
function useStore<TState, TSelected>(
store,
selector?,
options?): Accessor<TSelected>
```

Defined in: [index.tsx:16](https://github.com/TanStack/store/blob/main/packages/solid-store/src/index.tsx#L16)
Defined in: [index.tsx:21](https://github.com/TanStack/store/blob/main/packages/solid-store/src/index.tsx#L21)

### Type Parameters

Expand All @@ -59,6 +69,10 @@ Defined in: [index.tsx:16](https://github.com/TanStack/store/blob/main/packages/

(`state`) => `TSelected`

#### options?

`UseStoreOptions`\<`TSelected`\>

### Returns

`Accessor`\<`TSelected`\>
2 changes: 1 addition & 1 deletion docs/framework/svelte/reference/functions/shallow.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ title: shallow
function shallow<T>(objA, objB): boolean
```

Defined in: [index.svelte.ts:43](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L43)
Defined in: [index.svelte.ts:51](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L51)

## Type Parameters

Expand Down
22 changes: 18 additions & 4 deletions docs/framework/svelte/reference/functions/usestore.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ title: useStore
## Call Signature

```ts
function useStore<TState, TSelected>(store, selector?): object
function useStore<TState, TSelected>(
store,
selector?,
options?): object
```

Defined in: [index.svelte.ts:10](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L10)
Defined in: [index.svelte.ts:14](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L14)

### Type Parameters

Expand All @@ -31,6 +34,10 @@ Defined in: [index.svelte.ts:10](https://github.com/TanStack/store/blob/main/pac

(`state`) => `TSelected`

#### options?

`UseStoreOptions`\<`TSelected`\>

### Returns

`object`
Expand All @@ -44,10 +51,13 @@ readonly current: TSelected;
## Call Signature

```ts
function useStore<TState, TSelected>(store, selector?): object
function useStore<TState, TSelected>(
store,
selector?,
options?): object
```

Defined in: [index.svelte.ts:14](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L14)
Defined in: [index.svelte.ts:19](https://github.com/TanStack/store/blob/main/packages/svelte-store/src/index.svelte.ts#L19)

### Type Parameters

Expand All @@ -65,6 +75,10 @@ Defined in: [index.svelte.ts:14](https://github.com/TanStack/store/blob/main/pac

(`state`) => `TSelected`

#### options?

`UseStoreOptions`\<`TSelected`\>

### Returns

`object`
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/vue/reference/functions/shallow.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ title: shallow
function shallow<T>(objA, objB): boolean
```

Defined in: [index.ts:47](https://github.com/TanStack/store/blob/main/packages/vue-store/src/index.ts#L47)
Defined in: [index.ts:55](https://github.com/TanStack/store/blob/main/packages/vue-store/src/index.ts#L55)

## Type Parameters

Expand Down
22 changes: 18 additions & 4 deletions docs/framework/vue/reference/functions/usestore.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ title: useStore
## Call Signature

```ts
function useStore<TState, TSelected>(store, selector?): Readonly<Ref<TSelected, TSelected>>
function useStore<TState, TSelected>(
store,
selector?,
options?): Readonly<Ref<TSelected, TSelected>>
```

Defined in: [index.ts:12](https://github.com/TanStack/store/blob/main/packages/vue-store/src/index.ts#L12)
Defined in: [index.ts:16](https://github.com/TanStack/store/blob/main/packages/vue-store/src/index.ts#L16)

### Type Parameters

Expand All @@ -31,17 +34,24 @@ Defined in: [index.ts:12](https://github.com/TanStack/store/blob/main/packages/v

(`state`) => `TSelected`

#### options?

`UseStoreOptions`\<`TSelected`\>

### Returns

`Readonly`\<`Ref`\<`TSelected`, `TSelected`\>\>

## Call Signature

```ts
function useStore<TState, TSelected>(store, selector?): Readonly<Ref<TSelected, TSelected>>
function useStore<TState, TSelected>(
store,
selector?,
options?): Readonly<Ref<TSelected, TSelected>>
```

Defined in: [index.ts:16](https://github.com/TanStack/store/blob/main/packages/vue-store/src/index.ts#L16)
Defined in: [index.ts:21](https://github.com/TanStack/store/blob/main/packages/vue-store/src/index.ts#L21)

### Type Parameters

Expand All @@ -59,6 +69,10 @@ Defined in: [index.ts:16](https://github.com/TanStack/store/blob/main/packages/v

(`state`) => `TSelected`

#### options?

`UseStoreOptions`\<`TSelected`\>

### Returns

`Readonly`\<`Ref`\<`TSelected`, `TSelected`\>\>
10 changes: 9 additions & 1 deletion packages/react-store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,33 @@ export * from '@tanstack/store'
* @private
*/
export type NoInfer<T> = [T][T extends any ? 0 : never]
type EqualityFn<T> = (objA: T, objB: T) => boolean
interface UseStoreOptions<T> {
equal?: EqualityFn<T>
}

export function useStore<TState, TSelected = NoInfer<TState>>(
store: Store<TState, any>,
selector?: (state: NoInfer<TState>) => TSelected,
options?: UseStoreOptions<TSelected>,
): TSelected
export function useStore<TState, TSelected = NoInfer<TState>>(
store: Derived<TState, any>,
selector?: (state: NoInfer<TState>) => TSelected,
options?: UseStoreOptions<TSelected>,
): TSelected
export function useStore<TState, TSelected = NoInfer<TState>>(
store: Store<TState, any> | Derived<TState, any>,
selector: (state: NoInfer<TState>) => TSelected = (d) => d as any,
options: UseStoreOptions<TSelected> = {},
): TSelected {
const equal = options.equal ?? shallow
const slice = useSyncExternalStoreWithSelector(
store.subscribe,
() => store.state,
() => store.state,
selector,
shallow,
equal,
)

return slice
Expand Down
72 changes: 72 additions & 0 deletions packages/react-store/tests/index.test.tsx
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have only created a test for this in the react-store package. Not sure if I should add a test in every package?

Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,78 @@ describe('useStore', () => {
expect(getByText('Number rendered: 2')).toBeInTheDocument()
})

it('allow specifying custom equality function', async () => {
const store = new Store({
array: [
{ select: 0, ignore: 1 },
{ select: 0, ignore: 1 },
],
})

function deepEqual<T>(objA: T, objB: T) {
return JSON.stringify(objA) === JSON.stringify(objB)
}

function Comp() {
const storeVal = useStore(
store,
(state) => state.array.map(({ ignore, ...rest }) => rest),
{ equal: deepEqual },
)
const [fn] = useState(vi.fn)
fn()

const value = storeVal
.map((item) => item.select)
.reduce((total, num) => total + num, 0)

return (
<div>
<p>Number rendered: {fn.mock.calls.length}</p>
<p>Store: {value}</p>
<button
type="button"
onClick={() =>
store.setState((v) => ({
array: v.array.map((item) => ({
...item,
select: item.select + 5,
})),
}))
}
>
Update select
</button>
<button
type="button"
onClick={() =>
store.setState((v) => ({
array: v.array.map((item) => ({
...item,
ignore: item.ignore + 2,
})),
}))
}
>
Update ignored
</button>
</div>
)
}

const { getByText } = render(<Comp />)
expect(getByText('Store: 0')).toBeInTheDocument()
expect(getByText('Number rendered: 1')).toBeInTheDocument()

await user.click(getByText('Update select'))

await waitFor(() => expect(getByText('Store: 10')).toBeInTheDocument())
expect(getByText('Number rendered: 2')).toBeInTheDocument()

await user.click(getByText('Update ignored'))
expect(getByText('Number rendered: 2')).toBeInTheDocument()
})

it('works with mounted derived stores', async () => {
const store = new Store(0)

Expand Down
10 changes: 9 additions & 1 deletion packages/solid-store/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,32 @@ export * from '@tanstack/store'
* @private
*/
export type NoInfer<T> = [T][T extends any ? 0 : never]
type EqualityFn<T> = (objA: T, objB: T) => boolean
interface UseStoreOptions<T> {
equal?: EqualityFn<T>
}

export function useStore<TState, TSelected = NoInfer<TState>>(
store: Store<TState, any>,
selector?: (state: NoInfer<TState>) => TSelected,
options?: UseStoreOptions<TSelected>,
): Accessor<TSelected>
export function useStore<TState, TSelected = NoInfer<TState>>(
store: Derived<TState, any>,
selector?: (state: NoInfer<TState>) => TSelected,
options?: UseStoreOptions<TSelected>,
): Accessor<TSelected>
export function useStore<TState, TSelected = NoInfer<TState>>(
store: Store<TState, any> | Derived<TState, any>,
selector: (state: NoInfer<TState>) => TSelected = (d) => d as any,
options: UseStoreOptions<TSelected> = {},
): Accessor<TSelected> {
const [signal, setSignal] = createSignal(selector(store.state))
const equal = options.equal ?? shallow

const unsub = store.subscribe(() => {
const data = selector(store.state)
if (shallow(signal(), data)) {
if (equal(signal(), data)) {
return
}
setSignal(() => data)
Expand Down
Loading
Loading