Skip to content
Closed
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
13 changes: 3 additions & 10 deletions packages/utils/src/store/ReactStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,7 @@ import { useIsoLayoutEffect } from '../useIsoLayoutEffect';
import { NOOP } from '../empty';

/**
* A Store that supports controlled state keys.
*
* - Keys registered through {@link useControlledProp} become controlled when a non-undefined
* value is provided. Controlled keys mirror the incoming value and ignore local writes
* (via {@link set}, {@link apply}, or {@link update}).
* - When a key is uncontrolled, an optional default value is written once on first render.
* - Use {@link useSyncedValue} and {@link useSyncedValues} to synchronize external values/props into the
* store during a layout phase using {@link useIsoLayoutEffect}.
* A Store that supports controlled state keys, non-reactive values and provides utility methods for React.
*/
export class ReactStore<
State,
Expand Down Expand Up @@ -113,14 +106,14 @@ export class ReactStore<
this.controlledValues.set(key, isControlled);

if (!isControlled && !Object.is(this.state[key], defaultValue)) {
super.update({ ...(this.state as State), [key]: defaultValue } as State);
super.update({ ...this.state, [key]: defaultValue } as State);
}
}

useIsoLayoutEffect(() => {
if (isControlled && !Object.is(this.state[key], controlled)) {
// Set the internal state to match the controlled value.
super.update({ ...(this.state as State), [key]: controlled } as State);
super.update({ ...this.state, [key]: controlled } as State);
}
}, [key, controlled, defaultValue, isControlled]);
}
Expand Down
26 changes: 17 additions & 9 deletions packages/utils/src/store/Store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ type Listener<T> = (state: T) => void;
* It uses an observer pattern to notify subscribers when the state changes.
*/
export class Store<State> {
/**
* The internal state of the store.
* This property is mutable only within this class methods.
*/
private internalState: State;

/**
* The current state of the store.
* This property is updated immediately when the state changes as a result of calling {@link update}, {@link apply}, or {@link set}.
Expand All @@ -13,12 +19,14 @@ export class Store<State> {
*
* Do not modify properties in state directly. Instead, use the provided methods to ensure proper state management and listener notification.
*/
public state: State;
public get state() {
return this.internalState;
}

private listeners: Set<Listener<State>>;

constructor(state: State) {
this.state = state;
this.internalState = state;
this.listeners = new Set();
}

Expand All @@ -39,7 +47,7 @@ export class Store<State> {
* Returns the current state of the store.
*/
public getSnapshot = () => {
return this.state;
return this.internalState;
};

/**
Expand All @@ -48,8 +56,8 @@ export class Store<State> {
* @param newState The new state to set for the store.
*/
public update(newState: State) {
if (this.state !== newState) {
this.state = newState;
if (this.internalState !== newState) {
this.internalState = newState;
this.listeners.forEach((l) => l(newState));
}
}
Expand All @@ -61,8 +69,8 @@ export class Store<State> {
*/
public apply(changes: Partial<State>) {
for (const key in changes) {
if (!Object.is(this.state[key], changes[key])) {
this.update({ ...this.state, ...changes });
if (!Object.is(this.internalState[key], changes[key])) {
this.update({ ...this.internalState, ...changes });
return;
}
}
Expand All @@ -75,8 +83,8 @@ export class Store<State> {
* @param value The new value to set for the specified key.
*/
public set<T>(key: keyof State, value: T) {
if (!Object.is(this.state[key], value)) {
this.update({ ...this.state, [key]: value });
if (!Object.is(this.internalState[key], value)) {
this.update({ ...this.internalState, [key]: value });
}
}
}
Loading