|
1 | | -import { useCallback, useEffect, useMemo } from "react"; |
| 1 | +import { useCallback, useEffect, useMemo, useRef } from "react"; |
2 | 2 |
|
3 | 3 | import { zodResolver } from "@hookform/resolvers/zod"; |
4 | 4 | import { useRouter } from "next/router"; |
5 | 5 | import { SubmitHandler, useForm, useWatch } from "react-hook-form"; |
| 6 | +import { isDeepEqual } from "remeda"; |
6 | 7 |
|
7 | 8 | import { NATIVE_TOKEN_DECIMALS, NATIVE_TOKEN_ID } from "@/common/constants"; |
8 | 9 | import { campaignsContractClient } from "@/common/contracts/core/campaigns"; |
@@ -91,8 +92,15 @@ export const useCampaignForm = ({ campaignId, ftId, onUpdateSuccess }: CampaignF |
91 | 92 | ], |
92 | 93 | ); |
93 | 94 |
|
| 95 | + // Track previous cross-field errors to prevent infinite loops |
| 96 | + const prevCrossFieldErrorsRef = useRef<Record<string, { message: string } | undefined>>({}); |
| 97 | + |
94 | 98 | useEffect(() => { |
95 | | - const errors: Record<string, { message: string }> = {}; |
| 99 | + const errors: Record<string, { message: string } | undefined> = { |
| 100 | + min_amount: undefined, |
| 101 | + max_amount: undefined, |
| 102 | + target_amount: undefined, |
| 103 | + }; |
96 | 104 |
|
97 | 105 | // Validate min_amount vs max_amount |
98 | 106 | if (parsedMinAmount && parsedMaxAmount && parsedMinAmount > parsedMaxAmount) { |
@@ -127,18 +135,27 @@ export const useCampaignForm = ({ campaignId, ftId, onUpdateSuccess }: CampaignF |
127 | 135 | }; |
128 | 136 | } |
129 | 137 |
|
| 138 | + // Only update if errors have changed to prevent infinite loops |
| 139 | + if (isDeepEqual(prevCrossFieldErrorsRef.current, errors)) { |
| 140 | + return; |
| 141 | + } |
| 142 | + |
| 143 | + prevCrossFieldErrorsRef.current = errors; |
| 144 | + |
130 | 145 | // Clear errors only for fields that are now valid |
131 | | - ["min_amount", "max_amount", "target_amount"].forEach((field) => { |
| 146 | + (["min_amount", "max_amount", "target_amount"] as const).forEach((field) => { |
132 | 147 | if (!errors[field]) { |
133 | 148 | self.clearErrors(field as keyof Values); |
134 | 149 | } |
135 | 150 | }); |
136 | 151 |
|
137 | 152 | // Set all collected errors |
138 | 153 | Object.entries(errors).forEach(([field, error]) => { |
139 | | - self.setError(field as keyof Values, error); |
| 154 | + if (error) { |
| 155 | + self.setError(field as keyof Values, error); |
| 156 | + } |
140 | 157 | }); |
141 | | - }, [values, self, parsedMinAmount, parsedMaxAmount, parsedTargetAmount]); |
| 158 | + }, [parsedMinAmount, parsedMaxAmount, parsedTargetAmount, self]); |
142 | 159 |
|
143 | 160 | const timeToMilliseconds = (time: number) => { |
144 | 161 | return new Date(time).getTime(); |
|
0 commit comments