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.