Skip to content

Commit bfc98e7

Browse files
committed
feat(maci): cache joinedPoll data
1 parent 6f00478 commit bfc98e7

File tree

5 files changed

+114
-90
lines changed

5 files changed

+114
-90
lines changed

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ NEXT_PUBLIC_SECONDS_PER_BLOCK="1"
1414
NEXT_PUBLIC_COORDINATOR_SERVICE_URL="http://localhost:3000/v1"
1515

1616
# UX improvements
17-
NEXT_MINIMUM_START_DELAY_IN_SECONDS="60" # coordinator service scheduler needs a bit of time to process
17+
NEXT_MINIMUM_START_DELAY_IN_SECONDS="30"
1818

1919
# Network and services
2020
NEXT_PUBLIC_CHAIN_NAME="arbitrumSepolia"

plugins/maciVoting/components/PollCard.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const PollCard = ({ pollId }: { pollId: bigint }) => {
1818
const [voteOption, setVoteOption] = useState<VoteOption | undefined>(undefined);
1919

2020
const { data: { voteStartDate, tallied, voteEnded, disabled, results } = {} } = useGetPollData(pollId);
21-
const { joinPollFunction, hasJoinedPoll, joinedPollData } = useJoinPoll(pollId);
21+
const { joinPollFunction, hasJoinedPoll, joinedPollData, isLoading: isLoadingJoinedPoll } = useJoinPoll(pollId);
2222
const { voteFunction } = useVote();
2323

2424
useEffect(() => {
@@ -56,7 +56,7 @@ const PollCard = ({ pollId }: { pollId: bigint }) => {
5656
const { pollStateIndex, voiceCredits } = joinedPollData;
5757

5858
setVoteOption(option);
59-
voteFunction(pollId, pollStateIndex, voiceCredits, option)
59+
await voteFunction(pollId, pollStateIndex, voiceCredits, option)
6060
.catch((error) => {
6161
setError(error.message);
6262
})
@@ -69,14 +69,17 @@ const PollCard = ({ pollId }: { pollId: bigint }) => {
6969
);
7070

7171
const buttonMessage = useMemo(() => {
72+
if (isLoadingJoinedPoll) {
73+
return <PleaseWaitSpinner fullMessage="Checking user status..." />;
74+
}
7275
if (hasJoinedPoll) {
7376
return "Already joined poll";
7477
}
7578
if (isLoading) {
7679
return <PleaseWaitSpinner fullMessage="Joining poll..." />;
7780
}
7881
return "Join poll";
79-
}, [hasJoinedPoll, isLoading]);
82+
}, [isLoadingJoinedPoll, hasJoinedPoll, isLoading]);
8083

8184
if (voteEnded && !tallied)
8285
return (
@@ -140,7 +143,7 @@ const PollCard = ({ pollId }: { pollId: bigint }) => {
140143
In order to submit your vote you need to join the poll using your locally generated MACI public key and your
141144
authorized wallet.
142145
</p>
143-
<Button onClick={onClickJoinPoll} disabled={hasJoinedPoll || isLoading}>
146+
<Button onClick={onClickJoinPoll} disabled={hasJoinedPoll || isLoading || isLoadingJoinedPoll}>
144147
{buttonMessage}
145148
</Button>
146149
</div>

plugins/maciVoting/contexts/MaciContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export const MaciProvider = ({ children }: { children: ReactNode }) => {
8787

8888
setMaciKeypair(keypair);
8989
} catch (error) {
90-
setError("Error creating keypair");
90+
setError("Error creating keypair. Please go to MACI Voting and try again.");
9191
setIsLoading(false);
9292
return;
9393
}
Lines changed: 105 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,145 @@
11
import { PUBLIC_MACI_ADDRESS, PUBLIC_MACI_DEPLOYMENT_BLOCK } from "@/constants";
22
import { generateMaciStateTreeWithEndKey, getJoinedUserData, joinPoll } from "@maci-protocol/sdk/browser";
3-
import { useCallback, useEffect, useState } from "react";
3+
import { useCallback, useMemo, useState } from "react";
44
import { usePublicClient } from "wagmi";
55
import { DEFAULT_IVCP_DATA, DEFAULT_SG_DATA } from "../../contexts/MaciContext";
66
import { clientToSigner, useEthersSigner } from "../useEthersSigner";
77
import { useMaci } from "../useMaci";
88
import { type IJoinPollData } from "../../contexts/types";
9+
import { useQuery, useQueryClient } from "@tanstack/react-query";
910

1011
export const useJoinPoll = (pollId?: bigint) => {
1112
const signer = useEthersSigner();
1213
const publicClient = usePublicClient();
14+
const queryClient = useQueryClient();
1315
const { maciKeypair, isRegistered, stateIndex, artifacts } = useMaci();
1416

1517
const [isLoading, setIsLoading] = useState(false);
1618
const [error, setError] = useState<string | undefined>();
17-
const [hasJoinedPoll, setHasJoinedPoll] = useState(false);
18-
const [joinedPollData, setJoinedPollData] = useState<IJoinPollData | undefined>();
19+
// Keep track of newly joined poll data that will be returned when joining a poll
20+
const [newlyJoinedPollData, setNewlyJoinedPollData] = useState<IJoinPollData | undefined>();
1921

20-
// check if the user has joined the poll
21-
useEffect(() => {
22-
(async () => {
22+
// Query key for consistent cache access
23+
const joinedUserQueryKey = useMemo(
24+
() => ["joinedUserData", pollId?.toString(), maciKeypair?.publicKey.serialize()],
25+
[pollId, maciKeypair]
26+
);
27+
28+
// check if the user has joined the poll using useQuery
29+
const { data: joinedPollData, isLoading: isLoadingQuery } = useQuery({
30+
queryKey: joinedUserQueryKey,
31+
queryFn: async () => {
2332
if (!pollId || !publicClient || !maciKeypair || !isRegistered || !artifacts) {
24-
return;
33+
return null;
2534
}
2635

27-
// TODO: use useQuery for this
36+
setIsLoading(true);
37+
try {
38+
const publicSigner = clientToSigner(publicClient);
2839

29-
const publicSigner = clientToSigner(publicClient);
40+
const joinedUser = await getJoinedUserData({
41+
maciAddress: PUBLIC_MACI_ADDRESS,
42+
pollPublicKey: maciKeypair.publicKey.serialize(),
43+
signer: publicSigner,
44+
startBlock: PUBLIC_MACI_DEPLOYMENT_BLOCK,
45+
pollId,
46+
});
3047

31-
const joinedUser = await getJoinedUserData({
32-
maciAddress: PUBLIC_MACI_ADDRESS,
33-
pollPublicKey: maciKeypair.publicKey.serialize(),
34-
signer: publicSigner,
35-
startBlock: PUBLIC_MACI_DEPLOYMENT_BLOCK,
36-
pollId,
37-
}).catch((error) => {
48+
if (joinedUser && joinedUser.isJoined) {
49+
return {
50+
pollStateIndex: joinedUser.pollStateIndex ?? "",
51+
voiceCredits: joinedUser.voiceCredits ?? "0",
52+
// these two attributes are returned only when the user joins the poll
53+
nullifier: "",
54+
hash: "",
55+
};
56+
}
57+
} catch (error) {
3858
// eslint-disable-next-line no-console
39-
console.log("Error checking if user has joined poll", error);
40-
return;
41-
});
42-
43-
console.log("joinedUser", joinedUser);
44-
console.log("pollId", pollId);
45-
46-
if (joinedUser && joinedUser.isJoined) {
47-
setHasJoinedPoll(true);
48-
setJoinedPollData({
49-
pollStateIndex: joinedUser.pollStateIndex ?? "",
50-
voiceCredits: joinedUser.voiceCredits ?? "0",
51-
// these two attributes are returned only when the user joins the poll
52-
nullifier: "",
53-
hash: "",
54-
});
59+
console.error("Error checking if user has joined poll", error);
5560
}
56-
})();
57-
}, [artifacts, isRegistered, maciKeypair, pollId, publicClient]);
61+
62+
setIsLoading(false);
63+
return null;
64+
},
65+
enabled: Boolean(pollId && publicClient && maciKeypair && isRegistered && artifacts),
66+
staleTime: Infinity, // Cache forever as mentioned by user
67+
gcTime: Infinity, // Keep in cache forever
68+
});
5869

5970
const joinPollFunction = useCallback(async () => {
6071
if (!pollId || !signer || !maciKeypair || !isRegistered || !artifacts) {
61-
setHasJoinedPoll(false);
6272
return;
6373
}
6474

65-
const stateTree = await generateMaciStateTreeWithEndKey({
66-
maciContractAddress: PUBLIC_MACI_ADDRESS,
67-
signer,
68-
userPublicKey: maciKeypair.publicKey,
69-
startBlock: PUBLIC_MACI_DEPLOYMENT_BLOCK,
70-
});
71-
72-
const inclusionProof = stateTree.signUpTree.generateProof(Number(stateIndex));
73-
74-
const joinedData = await joinPoll({
75-
maciAddress: PUBLIC_MACI_ADDRESS,
76-
privateKey: maciKeypair.privateKey.serialize(),
77-
signer,
78-
pollId,
79-
inclusionProof: inclusionProof,
80-
pollJoiningZkey: artifacts.zKey as unknown as string,
81-
pollWasm: artifacts.wasm as unknown as string,
82-
sgDataArg: DEFAULT_SG_DATA,
83-
ivcpDataArg: DEFAULT_IVCP_DATA,
84-
blocksPerBatch: 1000,
85-
}).catch((error) => {
86-
if (error.message.includes("0xa3281672")) {
75+
if (joinedPollData) {
76+
setNewlyJoinedPollData(joinedPollData);
77+
}
78+
79+
setIsLoading(true);
80+
setError(undefined);
81+
82+
try {
83+
const stateTree = await generateMaciStateTreeWithEndKey({
84+
maciContractAddress: PUBLIC_MACI_ADDRESS,
85+
signer,
86+
userPublicKey: maciKeypair.publicKey,
87+
startBlock: PUBLIC_MACI_DEPLOYMENT_BLOCK,
88+
});
89+
90+
const inclusionProof = stateTree.signUpTree.generateProof(Number(stateIndex));
91+
92+
const joinedData = await joinPoll({
93+
maciAddress: PUBLIC_MACI_ADDRESS,
94+
privateKey: maciKeypair.privateKey.serialize(),
95+
signer,
96+
pollId,
97+
inclusionProof: inclusionProof,
98+
pollJoiningZkey: artifacts.zKey as unknown as string,
99+
pollWasm: artifacts.wasm as unknown as string,
100+
sgDataArg: DEFAULT_SG_DATA,
101+
ivcpDataArg: DEFAULT_IVCP_DATA,
102+
blocksPerBatch: 1000,
103+
});
104+
105+
if (joinedData) {
106+
setNewlyJoinedPollData(joinedData);
107+
// After successfully joining, manually invalidate the query to trigger a refetch
108+
queryClient.invalidateQueries({ queryKey: joinedUserQueryKey });
109+
}
110+
} catch (error: any) {
111+
if (error.message?.includes("0xa3281672")) {
87112
// 0xa3281672 -> signature of BalanceTooLow()
88113
setError(`Address balance is too low to join the poll`);
89-
setIsLoading(false);
90-
return;
114+
} else {
115+
// eslint-disable-next-line no-console
116+
console.error("Error joining poll", error);
117+
setError("Error joining poll");
91118
}
92-
// eslint-disable-next-line no-console
93-
console.log("Error joining poll", error);
94-
setError("Error joining poll");
95-
return;
96-
});
97-
98-
if (!joinedData) {
99-
setHasJoinedPoll(false);
100-
return;
119+
} finally {
120+
setIsLoading(false);
101121
}
122+
}, [
123+
pollId,
124+
signer,
125+
maciKeypair,
126+
isRegistered,
127+
artifacts,
128+
joinedPollData,
129+
stateIndex,
130+
queryClient,
131+
joinedUserQueryKey,
132+
]);
102133

103-
setHasJoinedPoll(true);
104-
setJoinedPollData(joinedData);
105-
}, [artifacts, isRegistered, maciKeypair, pollId, signer, stateIndex]);
134+
// Use the query data or the newly joined data if available
135+
const effectiveJoinedPollData = newlyJoinedPollData ?? joinedPollData;
136+
const effectiveHasJoinedPoll = Boolean(effectiveJoinedPollData);
106137

107138
return {
108-
isLoading,
139+
isLoading: isLoading || isLoadingQuery,
109140
error,
110-
hasJoinedPoll,
111-
joinedPollData,
141+
hasJoinedPoll: effectiveHasJoinedPoll,
142+
joinedPollData: effectiveJoinedPollData,
112143
joinPollFunction,
113144
};
114145
};

plugins/maciVoting/hooks/useGetPollData.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,6 @@ export const useGetPollData = (pollId?: string | bigint) => {
5656
// eslint-disable-next-line no-console
5757
console.log(error);
5858
}
59-
60-
/*
61-
const blockNumber = await publicSigner.provider.getBlockNumber();
62-
console.log("🔍 Current block number:", blockNumber);
63-
console.log("pollId", pollId);
64-
console.log("voteEnded", voteEnded);
65-
console.log("tallied", tallied);
66-
console.log("results", results);
67-
console.log("date", new Date());
68-
*/
6959
}
7060

7161
return {

0 commit comments

Comments
 (0)