Skip to content

Commit

Permalink
feat(plugin-status): ✨ enhance store status plugin and improve naming (
Browse files Browse the repository at this point in the history
…#78)

- Rename setUnmodifiedStatus to setInitializedStatus in effects.
- Introduce initialized() function to track store initialization status.
- Introduce resetStoreStatus() to manually reset store status.
- Deprecate isModified(), use modified() instead.
- Deprecate markAsUnmodified(), use resetStoreStatus() instead.
  • Loading branch information
zuriscript authored Jan 15, 2024
1 parent bdffa7f commit a3b74ef
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 36 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,14 @@ export const fetchBooksEffect = createEffect(
},
{
setLoadingStatus: true, // indicates that the store is loading while the effect runs
setUnmodifiedStatus: true, // it should mark the store as unmodified upon completion
setInitializedStatus: true, // it should mark the store as initialized upon completion
}
);
// And then run it
myBookStore.runEffect(fetchBooksEffect).subscribe();
const loadingSignal = isLoading(myBookStore); // true while effect is running
const isModifiedSignal = isModified(myBookStore); // true after store update
const initializedSignal = initialized(myBookStore); // true after initializing effect completion
const modifiedSignal = modified(myBookStore); // true after store update
```

## Sample Application
Expand Down
32 changes: 21 additions & 11 deletions docs/docs/plugins/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ sidebar_position: 3

# Store Status

Enable your store to track the loading and modification status. By utilizing the `StoreStatusPlugin`, you can monitor whether your store is currently loading (running an effect) and if it has been modified.
Enable your store to track the loading, initialization and modification status. By utilizing the `StoreStatusPlugin`, you can monitor whether your store is currently loading (running an effect) and if it has been initialized or modified.

## Enabling Store Status

Expand Down Expand Up @@ -148,36 +148,46 @@ const effectThatLoadsBooks = createEffect(
);
```

## Tracking Modification Status
## Tracking Initialization Status

The `isModified` function returns a Signal indicating whether the specified store has been modified.
The `initialized` function returns a Signal indicating whether the specified store has been initialized by an initializing effect.

```typescript
const modifiedSignal = isModified(store);
const initializedSignal = initialized(store);
```

A store is initially **unmodified**. Any command (`set`, `update`, `mutate`) applied to the store will mark it as **modified**. Additionally, an effect created with the `setUnmodifiedStatus` flag can reset the store's modification status back to **unmodified**.
A store is initially **not initialized** and any command (`set`, `update`, `mutate`) applied to the store will not change this status.
Only an effect created with the `setInitializedStatus` flag can set the store's initialization status to **initialized**.

```typescript
const effectThatResetsStore = createEffect(
const effectThatInitializesStore = createEffect(
'Load Books',
(store: BookStore): Observable<unknown> => {
return inject(BookService);
loadBooks().pipe(tap(books => store.setBookData(books.items)));
},
// highlight-start
{
setUnmodifiedStatus: true,
setInitializedStatus: true,
}
// highlight-end
);
```

### Manually Marking as Unmodified
## Tracking Modification Status

The `modified` function returns a Signal indicating whether the specified store has been modified.

```typescript
const modifiedSignal = modified(store);
```

A store is initially **unmodified**. Any command (`set`, `update`, `mutate`) applied to the store will mark it as **modified**. Additionally, an effect created with the `setInitializedStatus` flag can reset the store's modification status back to **unmodified**.

## Reset store status

In exceptional cases, you can manually mark a store as unmodified using the `markAsUnmodified` function. But in most scenarios, consider using the `setUnmodifiedStatus`
flag on the relevant effects to automatically manage the modification status.
In some cases, it could be necessary to manually reset the store status to `unmodified` and `deinitialized` using the `resetStoreStatus` function.

```typescript
markAsUnmodified(store);
resetStoreStatus(store);
```
5 changes: 3 additions & 2 deletions packages/signalstory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,14 @@ export const fetchBooksEffect = createEffect(
},
{
setLoadingStatus: true, // indicates that the store is loading while the effect runs
setUnmodifiedStatus: true, // it should mark the store as unmodified upon completion
setInitializedStatus: true, // it should mark the store as initialized upon completion
}
);
// And then run it
myBookStore.runEffect(fetchBooksEffect).subscribe();
const loadingSignal = isLoading(myBookStore); // true while effect is running
const isModifiedSignal = isModified(myBookStore); // true after store update
const initializedSignal = initialized(myBookStore); // true after initializing effect completion
const modifiedSignal = modified(myBookStore); // true after store update
```

## Sample Application
Expand Down
158 changes: 156 additions & 2 deletions packages/signalstory/src/__tests__/store-plugin-status.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import { delay, lastValueFrom, of, tap } from 'rxjs';
import { Store } from '../lib/store';
import { createEffect } from '../lib/store-effect';
import {
initialized,
isAnyEffectRunning,
isEffectRunning,
isLoading,
isModified,
markAsHavingNoRunningEffects,
markAsUnmodified,
modified,
resetStoreStatus,
runningEffects,
useStoreStatus,
} from '../lib/store-plugin-status/plugin-status';
Expand Down Expand Up @@ -374,6 +377,119 @@ describe('isModified', () => {
});
});

describe('modified', () => {
let store: Store<{ value: number }>;

beforeEach(() => {
store = new Store<{ value: number }>({
initialState: { value: 10 },
plugins: [useStoreStatus()],
});
});

it('should be unmodified initially', () => {
// assert
expect(modified(store)()).toBe(false);
});

it('should mark the store as modified after a command is processed', () => {
// act
store.set({ value: 10 });

// assert
expect(modified(store)()).toBe(true);
});

it('should mark the store as modified after an effect is processed', async () => {
// arrange
const effect = createEffect(
'dummyEffect',
(store: Store<{ value: number }>) =>
of(30).pipe(
tap(val =>
store.mutate(x => {
x.value = val;
})
)
)
);

// act
await lastValueFrom(store.runEffect(effect));

// assert
expect(modified(store)()).toBe(true);
});

it('should mark the store as unmodified after an effect with setInitializedStatus is processed', async () => {
// arrange
const effect = createEffect(
'dummyEffect',
(store: Store<{ value: number }>) =>
of(30).pipe(
tap(val =>
store.mutate(x => {
x.value = val;
})
)
),
{ setInitializedStatus: true }
);

// act
await lastValueFrom(store.runEffect(effect));

// assert
expect(modified(store)()).toBe(false);
});
});

describe('initialized', () => {
let store: Store<{ value: number }>;

beforeEach(() => {
store = new Store<{ value: number }>({
initialState: { value: 10 },
plugins: [useStoreStatus()],
});
});

it('should be deinitialized initially', () => {
// assert
expect(initialized(store)()).toBe(false);
});

it('should not mark the store as initialized after a command is processed', () => {
// act
store.set({ value: 10 });

// assert
expect(initialized(store)()).toBe(false);
});

it('should mark the store as initialized after an effect with setInitializedStatus is processed', async () => {
// arrange
const effect = createEffect(
'dummyEffect',
(store: Store<{ value: number }>) =>
of(30).pipe(
tap(val =>
store.mutate(x => {
x.value = val;
})
)
),
{ setInitializedStatus: true }
);

// act
await lastValueFrom(store.runEffect(effect));

// assert
expect(initialized(store)()).toBe(true);
});
});

describe('markAsUnmodified', () => {
let store: Store<{ value: number }>;

Expand All @@ -387,13 +503,51 @@ describe('markAsUnmodified', () => {
it('should manually mark the store as unmodified', () => {
// arrange
store.set({ value: 20 });
expect(isModified(store)()).toBe(true); // sanity check
expect(modified(store)()).toBe(true); // sanity check

// act
markAsUnmodified(store);

// assert
expect(isModified(store)()).toBe(false);
expect(modified(store)()).toBe(false);
});
});

describe('resetStoreStatus', () => {
let store: Store<{ value: number }>;

beforeEach(() => {
store = new Store<{ value: number }>({
initialState: { value: 10 },
plugins: [useStoreStatus()],
});
});

it('should reset the store modification status', () => {
// arrange
store.set({ value: 20 });
expect(modified(store)()).toBe(true); // sanity check

// act
resetStoreStatus(store);

// assert
expect(modified(store)()).toBe(false);
});

it('should reset the store initialization status', () => {
// arrange
const dummyEffect = createEffect('Dummy', () => {}, {
setInitializedStatus: true,
});
store.runEffect(dummyEffect);
expect(initialized(store)()).toBe(true); // sanity check

// act
resetStoreStatus(store);

// assert
expect(modified(store)()).toBe(false);
});
});

Expand Down
14 changes: 12 additions & 2 deletions packages/signalstory/src/lib/store-effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ export interface StoreEffectConfig {
setLoadingStatus?: boolean;

/**
* Indicates whether the effect sets unmodified status.
* @deprecated Use `setInitializedStatus` instead. This method is deprecated and will be removed in the next major release (v18).
*/
setUnmodifiedStatus?: boolean;

/**
* Indicates whether the effect sets initialized status.
* Only applicable if the `StoreStatus` plugin is used.
* Defaults to false.
*/
setUnmodifiedStatus?: boolean;
setInitializedStatus?: boolean;
}

/**
Expand Down Expand Up @@ -92,8 +97,13 @@ export function createEffect<
withInjectionContext:
!arg || arg === true || (arg.withInjectionContext ?? true),
setLoadingStatus: (arg as StoreEffectConfig)?.setLoadingStatus ?? false,
//TODO: remove the following line for next major update
setUnmodifiedStatus:
(arg as StoreEffectConfig)?.setUnmodifiedStatus ?? false,
setInitializedStatus:
(arg as StoreEffectConfig)?.setInitializedStatus ??
(arg as StoreEffectConfig)?.setUnmodifiedStatus ??
false,
},
};
}
Loading

0 comments on commit a3b74ef

Please sign in to comment.