commit 6d58d104a4827c3fa9510f847d10d596c2286a75
parent e8f224a3169d1bb0de05c10c80012f045b4b8e71
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date: Tue, 4 Apr 2023 00:59:59 +0200
fix: infinite useEffect dependency loop
Diffstat:
M | README.md | | | 20 | ++++++++++++++++++++ |
M | src/Leaderboard.js | | | 128 | +++++++++++++++++++++++++++++++++++++++++-------------------------------------- |
2 files changed, 86 insertions(+), 62 deletions(-)
diff --git a/README.md b/README.md
@@ -72,6 +72,26 @@ create trigger on_auth_user_created
for each row execute procedure public.handle_new_judge();
```
+Lastly, enable [PostgreSQL publication](https://www.postgresql.org/docs/current/logical-replication-publication.html) to enable [Supabase realtime feature for Postgres changes](https://supabase.com/docs/guides/realtime/extensions/postgres-changes):
+```sql
+begin;
+
+-- remove the supabase_realtime publication
+drop
+ publication if exists supabase_realtime;
+
+-- re-create the supabase_realtime publication with no tables
+create publication supabase_realtime;
+
+commit;
+
+-- add a table to the publication
+alter
+ publication supabase_realtime add table ratings;
+```
+
+This is required for the leaderboard to automatically update scores when they are created or changed by judges.
+
## Authentication with Magic Links
Authentication of judges is performed using [Supabase Magic Links](https://supabase.com/docs/guides/auth/auth-magic-link).
diff --git a/src/Leaderboard.js b/src/Leaderboard.js
@@ -128,14 +128,15 @@ async function newHeatFromLeaderboard(e, leaderboard, rankingComp, selectHeatRef
selectHeatRef.current.clearValue()
selectRankRef.current.clearValue()
alert('Created new heat "' + newHeatName + '" with top ' + newHeatSize + ' athletes')
+
+ // todo: put heatOpts and rankOptions in state/useEffect again
+ window.location.reload();
}
function Leaderboard() {
const [leaderboard, setLeaderboard] = useState([])
const [heatSelection, setHeatSelection] = useState([])
const [heats, setHeats] = useState([])
- const [heatOpts, setHeatOpts] = useState([])
- const [rankOpts, setRankOpts] = useState([])
const [rankingComp, setRankingComp] = useState([])
// state for new heat from top N leaderboard
@@ -145,78 +146,81 @@ function Leaderboard() {
const selectHeatRef = useRef();
const selectRankRef = useRef();
- // subscribe to ratings from judges and
- // reload all ratings to refresh leaderboard
- const channel = supabase.channel('ratings')
- channel.on(
- 'postgres_changes',
+ // add options to select or rank by heat
+ const heatOpts = heats.map(h => {
+ return {
+ value: h.id,
+ label: h.name
+ }
+ })
+
+ // // add static ranking options
+ const rankOptions = [
+ {
+ value: 'start',
+ label: 'Start Nr.'
+ }, {
+ value: 'best',
+ label: 'Best Heat'
+ }, {
+ value: 'worst',
+ label: 'Worst Heat'
+ }, {
+ value: 'total',
+ label: 'Total Sum (all heats)'
+ }
+ ]
+
+ // // add dynamic options to rank by best/worst heat
+ const heatOptions = heatOpts.map(h => {
+ return {
+ value: h.value,
+ label: "Sum " + h.label
+ }
+ })
+
+ const rankOpts = [
{
- event: '*',
- table: 'ratings',
+ label: "Overall",
+ options: rankOptions
},
- async (payload) => {
- const ratingSummary = await getRatingSummary(heatSelection.map(h => h.value))
- setLeaderboard(ratingSummary)
+ {
+ label: "Heat Sum",
+ options: heatOptions
}
- ).subscribe()
+ ]
useEffect(() => {
(async () => {
const heatList = await supabase.from('heats').select()
setHeats(heatList.data)
+ })();
+ }, []);
- if (heats) {
- // add options to select or rank by heat
- setHeatOpts(heats.map(h => {
- return {
- value: h.id,
- label: h.name
- }
- }))
-
- // add static ranking options
- const rankOptions = [
- {
- value: 'start',
- label: 'Start Nr.'
- }, {
- value: 'best',
- label: 'Best Heat'
- }, {
- value: 'worst',
- label: 'Worst Heat'
- }, {
- value: 'total',
- label: 'Total Sum (all heats)'
- }
- ]
-
- // add dynamic options to rank by best/worst heat
- const heatOptions = heatOpts.map(h => {
- return {
- value: h.value,
- label: "Sum " + h.label
- }
- })
-
- const groupedOptions = [
- {
- label: "Overall",
- options: rankOptions
- },
- {
- label: "Heat Sum",
- options: heatOptions
- }
- ];
-
- setRankOpts(groupedOptions)
- }
-
+ useEffect(() => {
+ (async () => {
+ // reload entire leaderboard when heat selection is changed
const ratingSummary = await getRatingSummary(heatSelection.map(h => h.value))
setLeaderboard(ratingSummary)
})();
- }, [heatSelection, heatOpts, heats]);
+ }, [heatSelection]);
+
+ useEffect(() => {
+ // subscribe to ratings from judges and
+ const channel = supabase.channel('ratings')
+ channel.on(
+ 'postgres_changes',
+ {
+ event: '*',
+ table: 'ratings',
+ },
+ async (payload) => {
+ // todo: reload only required ratings
+ const ratingSummary = await getRatingSummary(heatSelection.map(h => h.value))
+ setLeaderboard(ratingSummary)
+ }
+ ).subscribe()
+ }, [heatSelection]);
return (
<div className="Leaderboard">