Reputation: 3001
I was testing out useSyncExternalStore
to replace a store subscription used with useEffect
,
Basically this is the usage of the hook,
const state = BoardState.getInstance();
const boardUIstate = useSyncExternalStore(
(observer) => state.subscribe(observer),
() => getUiStateFromExternalState(state.getState())
);
The state
is a class implemented with classic observable pattern combine with a singleton,
export class BoardState<T> extends Subject<T> {
private static instance: BoardState<unknown> | undefined;
private constructor() {
super();
}
public static getInstance<T>(): BoardState<T> {
if (!BoardState.instance) {
BoardState.instance = new BoardState();
return BoardState.instance as BoardState<T>;
}
return BoardState.instance as BoardState<T>;
}
}
The extended Subject
class tracks the state and the observers & implements the subscribe - unsubscribe methods,
export class Subject<T> {
protected state: T | undefined;
private observers: any[] = [];
subscribe(fn: (newState: T | undefined) => void) {
this.observers.push(fn);
return () => this.unsubscribe(fn);
}
private unsubscribe(fn: (newState: T | undefined) => void) {
this.observers = this.observers.filter((ob) => ob !== fn);
}
setState(state: T) {
this.state = state;
this.observers.map((ob) => ob(this.state));
}
...
}
I can see on my local development server, it is complaining about this,
Warning: The result of getSnapshot should be cached to avoid an infinite loop
What's this caching ? How do I do that ? ( bug ? )
With the error,
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Above external state works with useEffect
& i can not see any issue with the implementation either. Any thoughts ?
Sandbox link
Upvotes: 1
Views: 2283
Reputation: 3001
My Bad, Found out that I need to use use-sync-external-store/with-selector
So I changed my code from,
const state = BoardState.getInstance();
const boardUIstate = useSyncExternalStore(
(observer) => state.subscribe(observer),
() => getUiStateFromExternalState(state.getState())
);
to
import {useSyncExternalStoreWithSelector} from 'use-sync-external-store/with-selector';
const state = BoardState.getInstance();
const boardUIstate = useSyncExternalStoreWithSelector<
Record<string, string | null> | undefined,
(number | null)[]
>(
state.subscribe.bind(state),
() => {
const state = state.getState();
return state;
},
undefined,
(snapshot) => {
return getUiStateFromExternalState(snapshot)
}
);
Upvotes: 2