Skip to content

Commit 3afc09d

Browse files
committed
improvement(react): replace unnecessary useEffect patterns with better React primitives
1 parent cef321b commit 3afc09d

File tree

24 files changed

+301
-308
lines changed

24 files changed

+301
-308
lines changed

apps/sim/app/(auth)/login/login-form.tsx

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useEffect, useState } from 'react'
3+
import { useEffect, useRef, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import { Eye, EyeOff } from 'lucide-react'
66
import Link from 'next/link'
@@ -99,15 +99,21 @@ export default function LoginPage({
9999
const router = useRouter()
100100
const searchParams = useSearchParams()
101101
const [isLoading, setIsLoading] = useState(false)
102-
const [_mounted, setMounted] = useState(false)
103102
const [showPassword, setShowPassword] = useState(false)
104103
const [password, setPassword] = useState('')
105104
const [passwordErrors, setPasswordErrors] = useState<string[]>([])
106105
const [showValidationError, setShowValidationError] = useState(false)
107106
const buttonClass = useBrandedButtonClass()
108107

109-
const [callbackUrl, setCallbackUrl] = useState('/workspace')
110-
const [isInviteFlow, setIsInviteFlow] = useState(false)
108+
const callbackUrlParam = searchParams?.get('callbackUrl')
109+
const invalidCallbackRef = useRef(false)
110+
if (callbackUrlParam && !validateCallbackUrl(callbackUrlParam) && !invalidCallbackRef.current) {
111+
invalidCallbackRef.current = true
112+
logger.warn('Invalid callback URL detected and blocked:', { url: callbackUrlParam })
113+
}
114+
const callbackUrl =
115+
callbackUrlParam && validateCallbackUrl(callbackUrlParam) ? callbackUrlParam : '/workspace'
116+
const isInviteFlow = searchParams?.get('invite_flow') === 'true'
111117

112118
const [forgotPasswordOpen, setForgotPasswordOpen] = useState(false)
113119
const [forgotPasswordEmail, setForgotPasswordEmail] = useState('')
@@ -120,30 +126,11 @@ export default function LoginPage({
120126
const [email, setEmail] = useState('')
121127
const [emailErrors, setEmailErrors] = useState<string[]>([])
122128
const [showEmailValidationError, setShowEmailValidationError] = useState(false)
123-
const [resetSuccessMessage, setResetSuccessMessage] = useState<string | null>(null)
124-
125-
useEffect(() => {
126-
setMounted(true)
127-
128-
if (searchParams) {
129-
const callback = searchParams.get('callbackUrl')
130-
if (callback) {
131-
if (validateCallbackUrl(callback)) {
132-
setCallbackUrl(callback)
133-
} else {
134-
logger.warn('Invalid callback URL detected and blocked:', { url: callback })
135-
}
136-
}
137-
138-
const inviteFlow = searchParams.get('invite_flow') === 'true'
139-
setIsInviteFlow(inviteFlow)
140-
141-
const resetSuccess = searchParams.get('resetSuccess') === 'true'
142-
if (resetSuccess) {
143-
setResetSuccessMessage('Password reset successful. Please sign in with your new password.')
144-
}
145-
}
146-
}, [searchParams])
129+
const [resetSuccessMessage, setResetSuccessMessage] = useState<string | null>(() =>
130+
searchParams?.get('resetSuccess') === 'true'
131+
? 'Password reset successful. Please sign in with your new password.'
132+
: null
133+
)
147134

148135
useEffect(() => {
149136
const handleKeyDown = (event: KeyboardEvent) => {

apps/sim/app/(auth)/reset-password/reset-password-content.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { Suspense, useEffect, useState } from 'react'
3+
import { Suspense, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import Link from 'next/link'
66
import { useRouter, useSearchParams } from 'next/navigation'
@@ -22,14 +22,9 @@ function ResetPasswordContent() {
2222
text: '',
2323
})
2424

25-
useEffect(() => {
26-
if (!token) {
27-
setStatusMessage({
28-
type: 'error',
29-
text: 'Invalid or missing reset token. Please request a new password reset link.',
30-
})
31-
}
32-
}, [token])
25+
const tokenError = !token
26+
? 'Invalid or missing reset token. Please request a new password reset link.'
27+
: null
3328

3429
const handleResetPassword = async (password: string) => {
3530
try {
@@ -87,8 +82,8 @@ function ResetPasswordContent() {
8782
token={token}
8883
onSubmit={handleResetPassword}
8984
isSubmitting={isSubmitting}
90-
statusType={statusMessage.type}
91-
statusMessage={statusMessage.text}
85+
statusType={tokenError ? 'error' : statusMessage.type}
86+
statusMessage={tokenError ?? statusMessage.text}
9287
/>
9388
</div>
9489

apps/sim/app/(auth)/signup/signup-form.tsx

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { Suspense, useEffect, useState } from 'react'
3+
import { Suspense, useMemo, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import { Eye, EyeOff } from 'lucide-react'
66
import Link from 'next/link'
@@ -82,49 +82,32 @@ function SignupFormContent({
8282
const searchParams = useSearchParams()
8383
const { refetch: refetchSession } = useSession()
8484
const [isLoading, setIsLoading] = useState(false)
85-
const [, setMounted] = useState(false)
8685
const [showPassword, setShowPassword] = useState(false)
8786
const [password, setPassword] = useState('')
8887
const [passwordErrors, setPasswordErrors] = useState<string[]>([])
8988
const [showValidationError, setShowValidationError] = useState(false)
90-
const [email, setEmail] = useState('')
89+
const [email, setEmail] = useState(() => searchParams.get('email') ?? '')
9190
const [emailError, setEmailError] = useState('')
9291
const [emailErrors, setEmailErrors] = useState<string[]>([])
9392
const [showEmailValidationError, setShowEmailValidationError] = useState(false)
94-
const [redirectUrl, setRedirectUrl] = useState('')
95-
const [isInviteFlow, setIsInviteFlow] = useState(false)
9693
const buttonClass = useBrandedButtonClass()
9794

95+
const redirectUrl = useMemo(
96+
() => searchParams.get('redirect') || searchParams.get('callbackUrl') || '',
97+
[searchParams]
98+
)
99+
const isInviteFlow = useMemo(
100+
() =>
101+
searchParams.get('invite_flow') === 'true' ||
102+
redirectUrl.startsWith('/invite/') ||
103+
redirectUrl.startsWith('/credential-account/'),
104+
[searchParams, redirectUrl]
105+
)
106+
98107
const [name, setName] = useState('')
99108
const [nameErrors, setNameErrors] = useState<string[]>([])
100109
const [showNameValidationError, setShowNameValidationError] = useState(false)
101110

102-
useEffect(() => {
103-
setMounted(true)
104-
const emailParam = searchParams.get('email')
105-
if (emailParam) {
106-
setEmail(emailParam)
107-
}
108-
109-
// Check both 'redirect' and 'callbackUrl' params (login page uses callbackUrl)
110-
const redirectParam = searchParams.get('redirect') || searchParams.get('callbackUrl')
111-
if (redirectParam) {
112-
setRedirectUrl(redirectParam)
113-
114-
if (
115-
redirectParam.startsWith('/invite/') ||
116-
redirectParam.startsWith('/credential-account/')
117-
) {
118-
setIsInviteFlow(true)
119-
}
120-
}
121-
122-
const inviteFlowParam = searchParams.get('invite_flow')
123-
if (inviteFlowParam === 'true') {
124-
setIsInviteFlow(true)
125-
}
126-
}, [searchParams])
127-
128111
const validatePassword = (passwordValue: string): string[] => {
129112
const errors: string[] = []
130113

apps/sim/app/chat/[identifier]/chat.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -190,16 +190,18 @@ export default function ChatClient({ identifier }: { identifier: string }) {
190190
return () => container.removeEventListener('scroll', handleScroll)
191191
}, [handleScroll])
192192

193-
useEffect(() => {
194-
if (isStreamingResponse) {
195-
setUserHasScrolled(false)
196-
197-
isUserScrollingRef.current = true
198-
setTimeout(() => {
199-
isUserScrollingRef.current = false
200-
}, 1000)
201-
}
202-
}, [isStreamingResponse])
193+
/**
194+
* Resets scroll tracking state when a new streaming response begins.
195+
* Suppresses scroll detection briefly to avoid false positives from
196+
* programmatic scrolls.
197+
*/
198+
const resetScrollStateForStreaming = useCallback(() => {
199+
setUserHasScrolled(false)
200+
isUserScrollingRef.current = true
201+
setTimeout(() => {
202+
isUserScrollingRef.current = false
203+
}, 1000)
204+
}, [])
203205

204206
const fetchChatConfig = async () => {
205207
try {
@@ -300,7 +302,7 @@ export default function ChatClient({ identifier }: { identifier: string }) {
300302
filesCount: files?.length,
301303
})
302304

303-
setUserHasScrolled(false)
305+
resetScrollStateForStreaming()
304306

305307
const userMessage: ChatMessage = {
306308
id: crypto.randomUUID(),

apps/sim/app/chat/components/input/input.tsx

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,6 @@ export const ChatInput: React.FC<{
7171
}
7272
}
7373

74-
// Adjust height on input change
75-
useEffect(() => {
76-
adjustTextareaHeight()
77-
}, [inputValue])
78-
7974
// Close the input when clicking outside (only when empty)
8075
useEffect(() => {
8176
const handleClickOutside = (event: MouseEvent) => {
@@ -94,17 +89,14 @@ export const ChatInput: React.FC<{
9489
return () => document.removeEventListener('mousedown', handleClickOutside)
9590
}, [inputValue])
9691

97-
// Handle focus and initial height when activated
98-
useEffect(() => {
99-
if (isActive && textareaRef.current) {
100-
textareaRef.current.focus()
101-
adjustTextareaHeight() // Adjust height when becoming active
102-
}
103-
}, [isActive])
104-
10592
const handleActivate = () => {
10693
setIsActive(true)
107-
// Focus is now handled by the useEffect above
94+
requestAnimationFrame(() => {
95+
if (textareaRef.current) {
96+
textareaRef.current.focus()
97+
adjustTextareaHeight()
98+
}
99+
})
108100
}
109101

110102
// Handle file selection
@@ -186,6 +178,7 @@ export const ChatInput: React.FC<{
186178

187179
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
188180
setInputValue(e.target.value)
181+
adjustTextareaHeight()
189182
}
190183

191184
// Handle voice start with smooth transition to voice-first mode

0 commit comments

Comments
 (0)