diff --git a/examples/react/functional-component-ts/src/App.tsx b/examples/react/functional-component-ts/src/App.tsx
index 5e49dc95..c69e67cc 100644
--- a/examples/react/functional-component-ts/src/App.tsx
+++ b/examples/react/functional-component-ts/src/App.tsx
@@ -3,6 +3,7 @@ import './App.css';
import { useAgile, useWatcher } from '@agile-ts/react';
import { useEvent } from '@agile-ts/event';
import {
+ COUNTUP,
MY_COLLECTION,
MY_COMPUTED,
MY_EVENT,
@@ -13,6 +14,7 @@ import {
import { globalBind } from '@agile-ts/core';
let rerenderCount = 0;
+let rerenderCountInCountupView = 0;
const App = (props: any) => {
// Note: Rerenders twice because of React Strickt Mode (also useState does trigger a rerender twice)
@@ -56,6 +58,19 @@ const App = (props: any) => {
globalBind('__core__', { ...require('./core') });
}, []);
+ const CountupView = () => {
+ const countup = useAgile(COUNTUP);
+ rerenderCountInCountupView++;
+ return (
+
+
Countup: {countup}
+
+ Rerender Count of count up View: {rerenderCountInCountupView}
+
+
+ );
+ };
+
return (
);
diff --git a/examples/react/functional-component-ts/src/core/index.ts b/examples/react/functional-component-ts/src/core/index.ts
index 705736db..28d6caca 100644
--- a/examples/react/functional-component-ts/src/core/index.ts
+++ b/examples/react/functional-component-ts/src/core/index.ts
@@ -30,6 +30,7 @@ App.registerStorage(
})
);
+export const COUNTUP = App.createState(1).interval((value) => value + 1, 1000);
export const MY_STATE = App.createState('MyState', { key: 'my-state' }); //.persist();
export const MY_STATE_2 = App.createState('MyState2', {
key: 'my-state2',
diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts
index 6b9cb056..ec8a10e4 100644
--- a/packages/core/src/state/index.ts
+++ b/packages/core/src/state/index.ts
@@ -42,6 +42,8 @@ export class State {
public watchers: { [key: string]: StateWatcherCallback } = {};
+ public currentInterval?: NodeJS.Timer | number;
+
/**
* @public
* State - Class that holds one Value and causes rerender on subscribed Components
@@ -402,10 +404,12 @@ export class State {
defaultStorageKey: null,
});
- if (this.persistent)
+ if (this.persistent) {
Agile.logger.warn(
- `By persisting the State '${this._key}' twice you overwrite the old Persistent Instance!`
+ `By persisting the State '${this._key}' twice you overwrite the old Persistent Instance!`,
+ this.persistent
);
+ }
// Create persistent -> Persist Value
this.persistent = new StatePersistent(this, {
@@ -428,19 +432,63 @@ export class State {
* @param callback - Callback Function
*/
public onLoad(callback: (success: boolean) => void): this {
- if (this.persistent) {
- this.persistent.onLoad = callback;
-
- // If State is already 'isPersisted' the loading was successful -> callback can be called
- if (this.isPersisted) callback(true);
- } else {
+ if (!this.persistent) {
Agile.logger.error(
`Please make sure you persist the State '${this._key}' before using the 'onLoad' function!`
);
+ return this;
}
+
+ this.persistent.onLoad = callback;
+
+ // If State is already 'isPersisted' the loading was successful -> callback can be called
+ if (this.isPersisted) callback(true);
+
return this;
}
+ //=========================================================================================================
+ // Interval
+ //=========================================================================================================
+ /**
+ * @public
+ * Calls callback at certain intervals in milliseconds and assigns the callback return value to the State
+ * @param callback- Callback that is called on each interval and should return the new State value
+ * @param ms - The intervals in milliseconds
+ */
+ public interval(
+ callback: (value: ValueType) => ValueType,
+ ms?: number
+ ): this {
+ if (this.currentInterval) {
+ Agile.logger.warn(
+ `You can only have one interval active!`,
+ this.currentInterval
+ );
+ return this;
+ }
+
+ this.currentInterval = setInterval(() => {
+ this.set(callback(this._value));
+ }, ms ?? 1000);
+
+ return this;
+ }
+
+ //=========================================================================================================
+ // Clear Interval
+ //=========================================================================================================
+ /**
+ * @public
+ * Clears the current Interval
+ */
+ public clearInterval(): void {
+ if (this.currentInterval) {
+ clearInterval(this.currentInterval as number);
+ delete this.currentInterval;
+ }
+ }
+
//=========================================================================================================
// Copy
//=========================================================================================================
diff --git a/packages/core/tests/unit/collection/selector.test.ts b/packages/core/tests/unit/collection/selector.test.ts
index 97736f9c..13f18143 100644
--- a/packages/core/tests/unit/collection/selector.test.ts
+++ b/packages/core/tests/unit/collection/selector.test.ts
@@ -18,7 +18,6 @@ describe('Selector Tests', () => {
dummyCollection = new Collection(dummyAgile);
jest.spyOn(Selector.prototype, 'select');
- console.warn = jest.fn();
});
it('should create Selector and call initial select (default config)', () => {
diff --git a/packages/core/tests/unit/state/state.test.ts b/packages/core/tests/unit/state/state.test.ts
index 86d89205..7094f34e 100644
--- a/packages/core/tests/unit/state/state.test.ts
+++ b/packages/core/tests/unit/state/state.test.ts
@@ -643,7 +643,8 @@ describe('State Tests', () => {
});
it('should overwrite existing persistent with a warning', () => {
- numberState.persistent = new StatePersistent(numberState);
+ const oldPersistent = new StatePersistent(numberState);
+ numberState.persistent = oldPersistent;
numberState.persist('newPersistentKey');
@@ -656,7 +657,8 @@ describe('State Tests', () => {
defaultStorageKey: null,
});
expect(console.warn).toHaveBeenCalledWith(
- `Agile Warn: By persisting the State '${numberState._key}' twice you overwrite the old Persistent Instance!`
+ `Agile Warn: By persisting the State '${numberState._key}' twice you overwrite the old Persistent Instance!`,
+ oldPersistent
);
});
});
@@ -694,6 +696,112 @@ describe('State Tests', () => {
});
});
+ describe('interval function tests', () => {
+ const dummyCallbackFunction = jest.fn();
+ const dummyCallbackFunction2 = jest.fn();
+
+ beforeEach(() => {
+ jest.useFakeTimers();
+ numberState.set = jest.fn();
+ });
+
+ afterEach(() => {
+ jest.clearAllTimers();
+ });
+
+ it('should create an interval (without custom milliseconds)', () => {
+ dummyCallbackFunction.mockReturnValueOnce(10);
+
+ numberState.interval(dummyCallbackFunction);
+
+ jest.runTimersToTime(1000); // travel 1000s in time -> execute interval
+
+ expect(setInterval).toHaveBeenCalledTimes(1);
+ expect(setInterval).toHaveBeenLastCalledWith(
+ expect.any(Function),
+ 1000
+ );
+ expect(dummyCallbackFunction).toHaveBeenCalledWith(numberState._value);
+ expect(numberState.set).toHaveBeenCalledWith(10);
+ expect(numberState.currentInterval).toEqual({
+ id: expect.anything(),
+ ref: expect.anything(),
+ unref: expect.anything(),
+ });
+ expect(console.warn).not.toHaveBeenCalled();
+ });
+
+ it('should create an interval (with custom milliseconds)', () => {
+ dummyCallbackFunction.mockReturnValueOnce(10);
+
+ numberState.interval(dummyCallbackFunction, 2000);
+
+ jest.runTimersToTime(2000); // travel 2000 in time -> execute interval
+
+ expect(setInterval).toHaveBeenCalledTimes(1);
+ expect(setInterval).toHaveBeenLastCalledWith(
+ expect.any(Function),
+ 2000
+ );
+ expect(dummyCallbackFunction).toHaveBeenCalledWith(numberState._value);
+ expect(numberState.set).toHaveBeenCalledWith(10);
+ expect(numberState.currentInterval).toEqual({
+ id: expect.anything(),
+ ref: expect.anything(),
+ unref: expect.anything(),
+ });
+ expect(console.warn).not.toHaveBeenCalled();
+ });
+
+ it("shouldn't be able to create second interval and print warning", () => {
+ numberState.interval(dummyCallbackFunction, 3000);
+ const currentInterval = numberState.currentInterval;
+ numberState.interval(dummyCallbackFunction2);
+
+ expect(setInterval).toHaveBeenCalledTimes(1);
+ expect(setInterval).toHaveBeenLastCalledWith(
+ expect.any(Function),
+ 3000
+ );
+ expect(numberState.currentInterval).toStrictEqual(currentInterval);
+ expect(console.warn).toHaveBeenCalledWith(
+ 'Agile Warn: You can only have one interval active!',
+ numberState.currentInterval
+ );
+ });
+ });
+
+ describe('clearInterval function tests', () => {
+ const dummyCallbackFunction = jest.fn();
+
+ beforeEach(() => {
+ jest.useFakeTimers();
+ numberState.set = jest.fn();
+ });
+
+ afterEach(() => {
+ jest.clearAllTimers();
+ });
+
+ it('should clear existing interval', () => {
+ numberState.interval(dummyCallbackFunction);
+ const currentInterval = numberState.currentInterval;
+
+ numberState.clearInterval();
+
+ expect(clearInterval).toHaveBeenCalledTimes(1);
+ expect(clearInterval).toHaveBeenLastCalledWith(currentInterval);
+ expect(numberState.currentInterval).toBeUndefined();
+ });
+
+ it("shouldn't clear not existing interval", () => {
+ numberState.clearInterval();
+
+ expect(clearInterval).not.toHaveBeenCalled();
+ expect(numberState.currentInterval).toBeUndefined();
+ });
+ });
+
describe('copy function tests', () => {
it('should return a reference free copy of the current State Value', () => {
jest.spyOn(Utils, 'copy');