From 617c8b9d56734e92f8afc4767db84a53594feea2 Mon Sep 17 00:00:00 2001 From: Henry Winkel Date: Sat, 16 May 2026 22:37:11 +0200 Subject: [PATCH] fix: prevent scroll reset by stabilizing useEffect dependencies with refs --- app/routes/analyze.tsx | 21 +++++++++++++++------ prisma/dev.db | Bin 40960 -> 40960 bytes 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/routes/analyze.tsx b/app/routes/analyze.tsx index 979e3fb..a6d91d3 100644 --- a/app/routes/analyze.tsx +++ b/app/routes/analyze.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef, useCallback } from "react"; import { Link } from "react-router"; import Navbar from "../components/Navbar"; import type { TradingDecision } from "../types/agents"; @@ -211,10 +211,16 @@ export default function Analyze() { }, []); // Auto-load indicators for stocks that don't have them yet (sequential with delay to avoid rate limits) + const loadingRef = useRef(false); + const stocksRef = useRef(stocks); + stocksRef.current = stocks; + useEffect(() => { - const unloaded = stocks.filter((s) => !s.indicatorsLoading && s.indicators.rsi == null); + if (loadingRef.current) return; + const unloaded = stocksRef.current.filter((s) => !s.indicatorsLoading && s.indicators.rsi == null); if (unloaded.length === 0) return; + loadingRef.current = true; let cancelled = false; const loadSequential = async () => { for (const stock of unloaded) { @@ -227,13 +233,14 @@ export default function Analyze() { } else { setStocks((s) => s.map((st) => st.id === stock.id ? { ...st, indicatorsLoading: false } : st)); } - // 500ms delay between each stock to avoid rate limiting await new Promise((r) => setTimeout(r, 500)); } + loadingRef.current = false; }; loadSequential(); return () => { cancelled = true; }; - }, [stocks]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); useEffect(() => { const interval = setInterval(() => { @@ -252,7 +259,8 @@ export default function Analyze() { }, 60000); return () => clearInterval(interval); - }, [stocks]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const loadIndicators = async (ticker: string) => { try { @@ -348,7 +356,8 @@ export default function Analyze() { }; updatePositions(); - }, [stocks.length]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const removeStock = async (id: string) => { const stock = stocks.find((s) => s.id === id); diff --git a/prisma/dev.db b/prisma/dev.db index eb13a26dc8eb2995d5182b62d21c369d81022ddb..7a27db2be9a8852554241014e67aff0c61a0c695 100644 GIT binary patch delta 1029 zcmY*YO=}cE5Zy$B!Kf>d1pFWr3yTVyap!A4l0yuFAczJO1W)$PRA!p=P7l*PyRI5n zL6WO7H*b1VPr-}gB?m7aJa`jC|ARM=R?n&+4D@t$)qAgAS3laHezZUR^z_uivon{b z7WVGExKN$5D+gwmDs$gvm#Xio@An?RIaGXT2i2Y9(?`}8Dl;=Do{V?uZt=5SKj>_K zoO^q!^Lcu{<@)qF-DThDxSdL+^1AR>?E?>f%@=$%JGOg!>QC_w+r3q+t)8_Xet+J} zNB3*2FY`p#Ry?m!(`*YjGP%iV?>fy!O=+24l}goCoNjTYQ=f7R**RT&U9FBcq~e;( z1QfrAkYShy7{X>I``|Z0OYl6%6Rwp7>)M3M4Gg)C!N7tbTwofUZnFV8CJe5&fGcn< z&#uEc-?l9(E7OR^_M4MHS`v?Ca=rn>m~@12$~yw;~{ZKIx7h*xJmA64B~_bOwffz2`!YBV6HRz z4Rbn|W4fXY1!p~hb1KhiFEPZ0WOvMJ5``VPfKz-EMcf^!9Aqwe4lM)pEScGl$EUO3%xc%wX-_(}ZZMB!3j_0~BFW&hLm8@EJ delta 1112 zcmYjQ&ubGw7)`1npw=SNL(#)$PZg4w{gKVCMXLCN9)yDRAfBAgPP3!4GwaN3(+D+H z5&VIms^waI|`_!3^8?{3>beet?MAN@37nWu# zHLrH0c+@I*jXtc*jeago*HhJgx?DXr>MS~kGFBV$MLjgEF)x*6X8!#A&4hu{yvHRG zV64hOC}Uv3jhwT<4OpeX@?K&=GeIohVE73vc4*Onc~u# zvn{v;eI7GCh9+9&G9H2(c41-f`7_6Bx&KOGjaiF@gi@8JDkjJeq|zz!S{FIBIc}h& zfqudTLp&}72%d7wWe+qr_gZj+#W^ir3x1h0xTYi+{-7iswjr0?njxg*9)cG|R+E@P zsx$+N;7o6nGq#}6#bO!Gr`EXzBI9xs?3JX)(sFdB3kmQQ#YhR4Kr$>xMUZ@Jers#h zYK3gpacEL>5MdgS3>gtN82}|F!O>K|QKFCeqP_+zdAR{9vBo7s32g!dX~xW0u#6^> zQ`9BT42s*BNU}l1SfdFW1H|Hdv^4F2dk*xp>LVg@H5sGUw481?i~=_Z{Z7|wI)3N} zl}h#NBu(Dv)ymv(b>-G-^{sRG<#zS0Tl=2>hJp1e6pAWgH7MO|07 z+ij=c;Gcu)aT%kER_SrMl31E@5#!^( z7lrNnx(?A_Mz9{CnWYJf8$dO~$e_#U58q@lsN4#rFpbN|v{)#TVew~xZ6a`v)W#Vm zeKJlj6^M%2AD^YdF^sL!(93yDBp$CkE6>C6mUDIgkURel`D}Xs^Ta_v^1>i&`d$#c Hzj)^_gX?w<