0

Im getting the error "Maximum update depth exceeded" using the following implementation:

export type InstrumentDetailBaseState =
  | BaseState
  | {
      type: 'success';
      detail: InstrumentDetail;
    };

export interface RealTimeInstruments {
  [key: string]: InstrumentDetailBaseState;
}

export interface MarketsState {
  details: RealTimeInstruments;
}

export const initialState: MarketsState = {
  details: {},
};

const slice = createSlice({
  name: '/markets',
  initialState,
  reducers: {
    updateInstrumentDetail: (
      state,
      { payload }: PayloadAction<UpdateInstrumentDetailRequest>,
    ) => {
      const current = state.details[payload.key];
      const detail = current.detail;
      state.details[payload.key] = {
          type: 'success',
          detail: Lodash.merge({}, detail, payload.detail),
      };
    },
  },
});

export const { updateInstrumentDetail } = slice.actions;

Inside my actions file i have this:

export const useUpdateInstrumentDetailAction = () => {
  const dispatch = useAppDispatch();
  return useCallback(
    (request: UpdateInstrumentDetailRequest) => {
      dispatch(updateInstrumentDetail(request));
    },
    [dispatch],
  );
};

This is my selector implementation:

export type AppSelector<Response, Request = null> = Selector<
  MainAppState,
  Response,
  Request
>;

const instrumentDetails: AppSelector<RealTimeInstruments> = ({
  marketsReducer,
}) => marketsReducer.details;

const instrumentDetailsKeyArgs: AppSelector<
  string,
  RealTimeInstrumentSelectorRequest
> = (_, { key }) => key;

const createDeepEqualSelector = createSelectorCreator(defaultMemoize, isEqual);

const createDetailItemSelector = () =>
  createDeepEqualSelector(
    instrumentDetails,
    instrumentDetailsKeyArgs,
    (details, key) => {
      const current = details[key];
      if (current?.type === 'success') {
        return current.detail;
      }
      return undefined;
    },
  );

export function useRealTimeInstrumentDetail(
  key: string,
  defaultItem?: InstrumentDetail | undefined,
): InstrumentDetail | undefined {
  const detailItemSelector = useMemo(createDetailItemSelector, []);

  const selector = useCallback(
    (state: MainAppState) => detailItemSelector(state, { key }),
    [key, detailItemSelector],
  );

  const item = useAppSelector(selector);

  return useMemo(
    () => Lodash.merge({}, defaultItem, item),
    [defaultItem, item],
  );
}

I use the lighstreamer socket to listen the stock market updates.

const useLightstreamerClient = () => useContext(LightstreamerContext);

export const useLightStreamerSubscription = (
  request: SubscriptionRequest,
  listener: SubscriptionListener,
) => {
  const client = useLightstreamerClient();

  useEffect(() => {
    const subscription = new Subscription(
      request.mode,
      request.items,
      request.fields,
    );
    subscription.setRequestedSnapshot('yes');
    subscription.addListener(listener);
    client?.subscribe(subscription);
    return () => {
      client?.unsubscribe(subscription);
    };
  }, [client, request, listener]);
};

Finaly i use this custom hook to generate the subscriptions.

In this case when lighstreamer sends multiple "ticks" or "updates" and the function onItemUpdate is called the system trhows the next error:

Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

Note: When i say "multiple" i mean between 1 and 100 updates per second for each subscription key.

export const useRealTimeInstrumentSubscription = (
  subscriptionKeys: string[],
) => {
  const updateInstrument = useUpdateInstrumentDetailAction();

  const subscriptionRequest = useMemo<SubscriptionRequest>(() => {
    return {
      mode: 'MERGE',
      items: subscriptionKeys,
      fields: [
        'id',
        'price',
        'volume',
      ],
    };
  }, [subscriptionKeys]);

  const subscriptionListener = useMemo<SubscriptionListener>(
    () => ({
      onItemUpdate: item => {
        const id: string = item.getValue('id');

        const priceValue = item.getValue('price');
        const volumeValue = item.getValue('volume');;


        const detail: RealTimeInstrumentDetail = {
          price: parseNumber(priceValue),
          volume: parseNumber(volumeValue),
        };

        updateInstrument({
          key: id,
          detail,
        });
      },
    }),
    [updateInstrument],
  );

  useLightStreamerSubscription(subscriptionRequest, subscriptionListener);
};

I use the subscriptions like this:

In this case the array of instruments is static and is not in the state.

const InstrumentsDataList: React.FC<{
  readonly instruments: Instrument[];
}> = ({ instruments }) => {

  const subscriptionKeys = useMemo(
    () => instruments.map(({ instrument }) => instrument.subscriptionKey),
    [instruments]
  );

  useRealTimeInstrumentSubscription(subscriptionKeys);

  return (
    <FlatList
      style={styles.container}
      data={instruments}
      keyExtractor={({ instrument }) => instrument.uniqueKey}
      renderItem={({ item }) => (
        <InstrumentDataListItem
          item={item}
        />
      )}
    />
  );
};

This is the list item:

const InstrumentDataListItem: React.FC<InstrumentDataListItemProps> = ({
  item,
}) => {
  const {
    price, volume
  } = useRealTimeInstrumentDetail(item.instrument.uniqueKey, item.detail);

  return <View>...</View>;
};

export default InstrumentDataListItem;

Even if i remove the FlatList the error persist.

const InstrumentsDataList: React.FC<{
  readonly instruments: Instrument[];
}> = ({ instruments }) => {

  const subscriptionKeys = useMemo(
    () => instruments.map(({ instrument }) => instrument.subscriptionKey),
    [instruments]
  );

  useRealTimeInstrumentSubscription(subscriptionKeys);

  return (
    <View/>
  );
};

The debugger tracks the error inside the "useRealTimeInstrumentSubscription" in the "onItemUpdate" function when the "updateInstrument" action is called.

I use the useRealTimeInstrumentDetail hook inside the list items for better performance. The selector is already memonized but the error persist.

0 Answers0