Skip to content

Commit

Permalink
forceNotify flag (#10)
Browse files Browse the repository at this point in the history
* Add test (it fails)

* Implement

* README

* Changeset

* Tweak README
  • Loading branch information
hmans authored Jul 26, 2022
1 parent f45d547 commit da27eba
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/spotty-planets-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"statery": minor
---

`set` now takes a second argument `forceNotify`; when set to true, all updated properties will be notified, regardless of referential equality to the previous value.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,28 @@ const BuyHouseButton = () => {
}
```

### Forcing a store update

When the store is updated, Statery will check which of the properties within the update object are actually different objects (or scalar values) from the previous state, and will only notify listeners to those properties.

In some cases, you may want to force a store update even though the property has not changed to a new object. For these situations, the `set` function allows you to pass a second argument; if this is set to `true`, Statery will ignore the equality check and notify all listeners to the properties included in the update.

Example:

```tsx
const store = makeStore({
rotation: new THREE.Vector3()
})

export const randomizeRotation = () =>
store.set(
(state) => ({
rotation: state.rotation.randomRotation()
}),
true
)
```

### Subscribing to updates (imperatively)

Use a store's `subscribe` function to register a callback that will be executed every time the store is changed.
Expand Down
14 changes: 11 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export type Store<T extends State = State> = {
*
* @see StateUpdateFunction
*/
set: (updates: Partial<T> | StateUpdateFunction<T>) => T
set: (updates: Partial<T> | StateUpdateFunction<T>, forceNotify?: boolean) => T

/**
* Subscribe to changes to the store's state. Every time the store is updated, the provided
Expand Down Expand Up @@ -100,9 +100,17 @@ export const makeStore = <T extends State>(initialState: T): Store<T> => {
return state
},

set: (incoming) => {
set: (incoming, forceNotify = false) => {
/* If the argument is a function, run it */
const updates = getActualChanges(incoming instanceof Function ? incoming(state) : incoming)
const incomingState = incoming instanceof Function ? incoming(state) : incoming

/*
Check which updates we're actually applying. If forceNotify is enabled,
we'll use (and notify for) all of them; otherwise, we'll check them against
the current state to only change (and notify for) the properties
that have changed from the current state.
*/
const updates = forceNotify ? incomingState : getActualChanges(incomingState)

/* Has anything changed? */
if (Object.keys(updates).length > 0) {
Expand Down
19 changes: 17 additions & 2 deletions test/store.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { makeStore } from "../src"

describe("makeStore", () => {
const store = makeStore({
const init = () => ({
foo: 0,
bar: 0,
active: false
})

const store = makeStore(init())

beforeEach(() => {
store.set({ foo: 0, bar: 0 })
store.set(init)
})

describe(".state", () => {
Expand Down Expand Up @@ -117,6 +119,19 @@ describe("makeStore", () => {
store.unsubscribe(listener)
})

it("receives all changes made to the store if the `force` flag is set", () => {
const listener = jest.fn()
store.subscribe(listener)

/* We're setting both foo and bar; only foo is actually a new value. */
store.set({ foo: 1, bar: 0 }, true)

/* Since we've forced the update, the changes now include the un-changed `bar`, as well */
expect(listener.mock.calls[0][0]).toEqual({ foo: 1, bar: 0 })

store.unsubscribe(listener)
})

it("already makes the updated state available to listeners", () => {
let newValue: number | undefined = undefined
let prevValue: number | undefined = undefined
Expand Down

0 comments on commit da27eba

Please sign in to comment.