refactor: use lazy useEnsData for treasury vote ENS resolution#572
refactor: use lazy useEnsData for treasury vote ENS resolution#572
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Replace async useEffect that blocked rendering behind ENS lookups with synchronous useMemo for vote merging. ENS names now resolve lazily per-row via the existing useEnsData hook, matching the pattern used by OrchestratorList and other components. Co-authored-By: ECWireless <elliott@coopallc.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fb7ee7a to
d0093dd
Compare
There was a problem hiding this comment.
Pull request overview
Refactors treasury proposal vote rendering to resolve ENS names lazily via the existing useEnsData SWR hook, removing the previous blocking ENS fetch/caching approach so vote rows can render immediately with truncated addresses while ENS resolves per-row.
Changes:
- Removed the custom ENS cache + async
decorateVotesflow fromTreasuryVoteTable, replacing it with synchronous vote decoration. - Updated vote row components to call
useEnsData()per voter for lazy ENS display. - Simplified the
Votetype and selection/popover wiring to no longer passensNamethrough props.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| components/Treasury/TreasuryVoteTable/index.tsx | Removes ENS blocking logic and decorates votes synchronously from vote + event data. |
| components/Treasury/TreasuryVoteTable/Views/VoteItem.tsx | Resolves ENS lazily per row (mobile + desktop vote views) and updates onSelect payload. |
| components/Treasury/TreasuryVoteTable/Views/DesktopVoteTable.tsx | Removes ensName from Vote and adjusts the voter column/accessor + onSelect signature. |
| components/Treasury/TreasuryVoteTable/TreasuryVotePopover.tsx | Resolves ENS internally via useEnsData() instead of receiving it via props. |
Comments suppressed due to low confidence (1)
components/Treasury/TreasuryVoteTable/Views/DesktopVoteTable.tsx:45
react-tablesorting uses the columnaccessorvalue. Withaccessor: "voter", the accessor returns an object, so sorting the “Voter” column will effectively compare"[object Object]"for every row (i.e., sorting won’t work correctly). Use an accessor that returns a primitive (e.g., voter id string) and keep the customCellfor renderingEthAddressBadge.
{
Header: "Voter",
accessor: "voter",
id: "voter",
Cell: ({ row }) => (
<Box css={{ minWidth: 120 }}>
<EthAddressBadge value={row.original.voter.id} />
</Box>
),
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return votesList.map((vote) => { | ||
| const events = eventsList | ||
| .filter((event) => event.voter.id === vote.voter.id) | ||
| .sort((a, b) => b.timestamp - a.timestamp); | ||
|
|
||
| const latestEvent = events[0]; | ||
|
|
||
| return { | ||
| ...vote, | ||
| reason: latestEvent?.reason || vote.reason || "", | ||
| transactionHash: latestEvent?.transaction.id ?? "", | ||
| timestamp: latestEvent?.timestamp, | ||
| }; | ||
| }) as Vote[]; |
There was a problem hiding this comment.
useVotes currently does an eventsList.filter(...).sort(...) for every vote, and only uses events[0]. This is O(votes × events log events) and will get expensive as vote/event counts grow. Consider building a single Map<voterId, latestEvent> in one pass over eventsList (or sorting eventsList once) and then decorating votes via a lookup, avoiding per-vote sorts/filters.
| timestamp: latestEvent?.timestamp, | ||
| }; | ||
| }) as Vote[]; | ||
| }, [treasuryVotesData, treasuryVoteEventsData]); |
There was a problem hiding this comment.
The useMemo dependency array is [treasuryVotesData, treasuryVoteEventsData], but Apollo often returns new wrapper objects even when the underlying lists are unchanged. That can cause this memo to recompute on most renders. Depending on treasuryVotesData?.treasuryVotes and treasuryVoteEventsData?.treasuryVoteEvents (or votesList/eventsList references) would make the memoization effective.
| }, [treasuryVotesData, treasuryVoteEventsData]); | |
| }, [treasuryVotesData?.treasuryVotes, treasuryVoteEventsData?.treasuryVoteEvents]); |
| timestamp: latestEvent?.timestamp, | ||
| }; | ||
| }) as Vote[]; | ||
| }, [treasuryVotesData, treasuryVoteEventsData]); |
There was a problem hiding this comment.
| return votesList.map((vote) => { | ||
| const events = eventsList | ||
| .filter((event) => event.voter.id === vote.voter.id) | ||
| .sort((a, b) => b.timestamp - a.timestamp); | ||
|
|
||
| const latestEvent = events[0]; |
There was a problem hiding this comment.
| return votesList.map((vote) => { | |
| const events = eventsList | |
| .filter((event) => event.voter.id === vote.voter.id) | |
| .sort((a, b) => b.timestamp - a.timestamp); | |
| const latestEvent = events[0]; | |
| // Build a map of voter ID to latest event in a single pass | |
| // First sort events by timestamp descending, then build map (first event per voter is latest) | |
| const sortedEvents = [...eventsList].sort( | |
| (a, b) => b.timestamp - a.timestamp | |
| ); | |
| const latestEventByVoter = new Map(); | |
| for (const event of sortedEvents) { | |
| if (!latestEventByVoter.has(event.voter.id)) { | |
| latestEventByVoter.set(event.voter.id, event); | |
| } | |
| } | |
| return votesList.map((vote) => { | |
| const latestEvent = latestEventByVoter.get(vote.voter.id); |
The useVotes hook has O(votes × events log events) performance issue from filtering and sorting events for each vote independently
| () => [ | ||
| { | ||
| Header: "Voter", | ||
| accessor: "ensName", |
Summary
Promise.allinuseEffect) in feat: improve ens caching of treasury votes table #561 with the existinguseEnsDataSWR hook used across the rest of the appdecorateVotesfunction, and thevotesLoadingstateWhat changed
TreasuryVoteTable/index.tsx— removed ENS cache, replaceduseEffect/decorateVoteswith synchronoususeMemoViews/VoteItem.tsx—MobileVoteViewandDesktopVoteViewnow calluseEnsData()per-row for lazy ENSViews/DesktopVoteTable.tsx— removedensNamefromVotetype andonSelectsignatureTreasuryVotePopover.tsx— resolves ENS internally viauseEnsData()instead of receiving it as a propWhy
The previous approach blocked the entire vote table render behind
Promise.all()of ENS lookups — even on revisits, every voter's ENS name had to re-resolve before anything displayed. This made the table feel slow despite having the vote data ready.Replaces #561
Test plan
🤖 Generated with Claude Code