Dilshan
Dilshan

Reputation: 3001

React JS - Error Maximum update depth exceeded

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

Answers (1)

Dilshan
Dilshan

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

Related Questions