Expose subscribe / unsubscribe functions from Atom

Summary: Allow subscribing to Atom state changes

Reviewed By: mweststrate

Differential Revision: D28027692

fbshipit-source-id: 24fd7ea16b013c364bbb1d25b30c48bc698db014
This commit is contained in:
Anton Nikolaev
2021-04-27 09:30:11 -07:00
committed by Facebook GitHub Bot
parent dcc7e06afc
commit 140cf38ffd
3 changed files with 90 additions and 8 deletions

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {createState} from '../atom';
test('can subscribe to atom state changes', () => {
const state = createState('abc');
let receivedValue: string | undefined;
let receivedPrevValue: string | undefined;
const unsubscribe = state.subscribe((value, prevValue) => {
receivedValue = value;
receivedPrevValue = prevValue;
});
try {
state.set('def');
expect(receivedValue).toBe('def');
expect(receivedPrevValue).toBe('abc');
state.set('ghi');
expect(receivedValue).toBe('ghi');
expect(receivedPrevValue).toBe('def');
} finally {
unsubscribe();
}
});
test('can unsubscribe from atom state changes', () => {
const state = createState('abc');
let receivedValue: string | undefined;
let receivedPrevValue: string | undefined;
const unsubscribe = state.subscribe((value, prevValue) => {
receivedValue = value;
receivedPrevValue = prevValue;
});
try {
state.set('def');
expect(receivedValue).toBe('def');
expect(receivedPrevValue).toBe('abc');
} finally {
unsubscribe();
}
state.set('ghi');
expect(receivedValue).toBe('def');
expect(receivedPrevValue).toBe('abc');
});
test('can unsubscribe from atom state changes using unsubscribe method', () => {
const state = createState('abc');
let receivedValue: string | undefined;
let receivedPrevValue: string | undefined;
const listener = (value: string, prevValue: string) => {
receivedValue = value;
receivedPrevValue = prevValue;
};
state.subscribe(listener);
try {
state.set('def');
expect(receivedValue).toBe('def');
expect(receivedPrevValue).toBe('abc');
} finally {
state.unsubscribe(listener);
}
state.set('ghi');
expect(receivedValue).toBe('def');
expect(receivedPrevValue).toBe('abc');
});

View File

@@ -17,11 +17,13 @@ export type Atom<T> = {
get(): T;
set(newValue: T): void;
update(recipe: (draft: Draft<T>) => void): void;
subscribe(listener: (value: T, prevValue: T) => void): () => void;
unsubscribe(listener: (value: T, prevValue: T) => void): void;
};
class AtomValue<T> implements Atom<T>, Persistable {
value: T;
listeners: ((value: T) => void)[] = [];
listeners: ((value: T, prevValue: T) => void)[] = [];
constructor(initialValue: T) {
this.value = initialValue;
@@ -33,8 +35,9 @@ class AtomValue<T> implements Atom<T>, Persistable {
set(nextValue: T) {
if (nextValue !== this.value) {
const prevValue = this.value;
this.value = nextValue;
this.notifyChanged();
this.notifyChanged(prevValue);
}
}
@@ -50,16 +53,17 @@ class AtomValue<T> implements Atom<T>, Persistable {
this.set(produce(this.value, recipe));
}
notifyChanged() {
notifyChanged(prevValue: T) {
// TODO: add scheduling
this.listeners.slice().forEach((l) => l(this.value));
this.listeners.slice().forEach((l) => l(this.value, prevValue));
}
subscribe(listener: (value: T) => void) {
subscribe(listener: (value: T, prevValue: T) => void) {
this.listeners.push(listener);
return () => this.unsubscribe(listener);
}
unsubscribe(listener: (value: T) => void) {
unsubscribe(listener: (value: T, prevValue: T) => void) {
const idx = this.listeners.indexOf(listener);
if (idx !== -1) {
this.listeners.splice(idx, 1);