Date:
As a product engineer, I've discovered how generative AI is transforming my code refactoring process. I'm not claiming AI is a cure-all, but I want to share a practical example that many developers facing messy component states will understand.
I was working on a multi-step form component "PinContent" when I realized my simple implementation had become a maze of useState declarations and if statements.
import React, { useState } from 'react'; | |
interface UserData { | |
username: string; | |
email: string; | |
avatarUrl?: string; | |
bio?: string; | |
} | |
export const ProfileSetup: React.FC = () => { | |
// Multiple state declarations for different steps | |
const [isEmailVerified, setIsEmailVerified] = useState(false); | |
const [isUsernameValid, setIsUsernameValid] = useState(false); | |
const [showAvatarUpload, setShowAvatarUpload] = useState(false); | |
const [isAvatarUploaded, setIsAvatarUploaded] = useState(false); | |
const [showBioForm, setShowBioForm] = useState(false); | |
const [isBioComplete, setIsBioComplete] = useState(false); | |
// Form data | |
const [email, setEmail] = useState(''); | |
const [username, setUsername] = useState(''); | |
const [avatarUrl, setAvatarUrl] = useState<string>(); | |
const [bio, setBio] = useState(''); | |
const handleEmailSubmit = async () => { | |
// Simulated email verification | |
const isValid = await validateEmail(email); | |
setIsEmailVerified(isValid); | |
if (isValid) { | |
setShowAvatarUpload(true); | |
} | |
}; | |
const handleUsernameSubmit = async () => { | |
// Simulated username validation | |
const isValid = await checkUsername(username); | |
setIsUsernameValid(isValid); | |
if (isValid && isEmailVerified) { | |
setShowAvatarUpload(true); | |
} | |
}; | |
const handleAvatarUpload = (url: string) => { | |
setAvatarUrl(url); | |
setIsAvatarUploaded(true); | |
setShowBioForm(true); | |
}; | |
const handleBioSubmit = () => { | |
setIsBioComplete(true); | |
}; | |
const handleSubmitAll = () => { | |
if (isEmailVerified && isUsernameValid && isAvatarUploaded && isBioComplete) { | |
const userData: UserData = { | |
username, | |
email, | |
avatarUrl, | |
bio, | |
}; | |
submitToServer(userData); | |
} | |
}; | |
return ( | |
<div className="profile-setup"> | |
{!isEmailVerified && ( | |
<EmailForm | |
email={email} | |
onEmailChange={setEmail} | |
onSubmit={handleEmailSubmit} | |
/> | |
)} | |
{isEmailVerified && !isUsernameValid && ( | |
<UsernameForm | |
username={username} | |
onUsernameChange={setUsername} | |
onSubmit={handleUsernameSubmit} | |
/> | |
)} | |
{isUsernameValid && showAvatarUpload && !isAvatarUploaded && ( | |
<AvatarUpload onUpload={handleAvatarUpload} /> | |
)} | |
{isAvatarUploaded && showBioForm && !isBioComplete && ( | |
<BioForm | |
bio={bio} | |
onBioChange={setBio} | |
onSubmit={handleBioSubmit} | |
/> | |
)} | |
{isBioComplete && ( | |
<button onClick={handleSubmitAll}>Complete Setup</button> | |
)} | |
</div> | |
); | |
}; | |
// Mock functions | |
const validateEmail = async (email: string): Promise<boolean> => true; | |
const checkUsername = async (username: string): Promise<boolean> => true; | |
const submitToServer = (userData: UserData): void => {}; |
This is a common situation - requirements evolve, and our components grow messier over time. I needed to add a new step between user validation and profile picture selection, leaving me with two options:
Here's a critical tip: Always commit your working changes before starting any AI-assisted refactoring. I've learned this lesson through experience. Without a committed baseline, you risk losing your last working state if the AI suggestions don't pan out.
I currently use Cursor with Claude Sonnet 3.5. While the tool offers various features, I've found Composer handles 75% of my AI interactions, especially for major refactoring work.
Here's my process:
Cursor presents changes as a diff, similar to a pull request review. This familiar format makes the process more intuitive and manageable.
Across different AI models, the suggested approach remains consistent:
import React, { useReducer } from 'react'; | |
interface UserData { | |
username: string; | |
email: string; | |
avatarUrl?: string; | |
bio?: string; | |
} | |
// Define possible form states | |
enum SetupState { | |
EMAIL_VERIFICATION = 'EMAIL_VERIFICATION', | |
USERNAME_VALIDATION = 'USERNAME_VALIDATION', | |
AVATAR_UPLOAD = 'AVATAR_UPLOAD', | |
BIO_ENTRY = 'BIO_ENTRY', | |
COMPLETE = 'COMPLETE', | |
} | |
// Define state shape | |
interface SetupFormState { | |
currentState: SetupState; | |
data: { | |
email: string; | |
username: string; | |
avatarUrl?: string; | |
bio?: string; | |
}; | |
} | |
// Define possible actions | |
type SetupAction = | |
| { type: 'SET_EMAIL'; payload: string } | |
| { type: 'EMAIL_VERIFIED' } | |
| { type: 'SET_USERNAME'; payload: string } | |
| { type: 'USERNAME_VERIFIED' } | |
| { type: 'AVATAR_UPLOADED'; payload: string } | |
| { type: 'SET_BIO'; payload: string } | |
| { type: 'BIO_COMPLETED' }; | |
// Initial state | |
const initialState: SetupFormState = { | |
currentState: SetupState.EMAIL_VERIFICATION, | |
data: { | |
email: '', | |
username: '', | |
}, | |
}; | |
// Reducer function | |
function setupReducer(state: SetupFormState, action: SetupAction): SetupFormState { | |
switch (action.type) { | |
case 'SET_EMAIL': | |
return { | |
...state, | |
data: { ...state.data, email: action.payload }, | |
}; | |
case 'EMAIL_VERIFIED': | |
return { | |
...state, | |
currentState: SetupState.USERNAME_VALIDATION, | |
}; | |
case 'SET_USERNAME': | |
return { | |
...state, | |
data: { ...state.data, username: action.payload }, | |
}; | |
case 'USERNAME_VERIFIED': | |
return { | |
...state, | |
currentState: SetupState.AVATAR_UPLOAD, | |
}; | |
case 'AVATAR_UPLOADED': | |
return { | |
...state, | |
currentState: SetupState.BIO_ENTRY, | |
data: { ...state.data, avatarUrl: action.payload }, | |
}; | |
case 'SET_BIO': | |
return { | |
...state, | |
data: { ...state.data, bio: action.payload }, | |
}; | |
case 'BIO_COMPLETED': | |
return { | |
...state, | |
currentState: SetupState.COMPLETE, | |
}; | |
default: | |
return state; | |
} | |
} | |
export const ProfileSetup: React.FC = () => { | |
const [state, dispatch] = useReducer(setupReducer, initialState); | |
const handleEmailSubmit = async () => { | |
const isValid = await validateEmail(state.data.email); | |
if (isValid) { | |
dispatch({ type: 'EMAIL_VERIFIED' }); | |
} | |
}; | |
const handleUsernameSubmit = async () => { | |
const isValid = await checkUsername(state.data.username); | |
if (isValid) { | |
dispatch({ type: 'USERNAME_VERIFIED' }); | |
} | |
}; | |
const handleAvatarUpload = (url: string) => { | |
dispatch({ type: 'AVATAR_UPLOADED', payload: url }); | |
}; | |
const handleBioSubmit = () => { | |
dispatch({ type: 'BIO_COMPLETED' }); | |
}; | |
const handleSubmitAll = () => { | |
if (state.currentState === SetupState.COMPLETE) { | |
submitToServer(state.data); | |
} | |
}; | |
// Render current step based on state | |
const renderCurrentStep = () => { | |
switch (state.currentState) { | |
case SetupState.EMAIL_VERIFICATION: | |
return ( | |
<EmailForm | |
email={state.data.email} | |
onEmailChange={(email) => dispatch({ type: 'SET_EMAIL', payload: email })} | |
onSubmit={handleEmailSubmit} | |
/> | |
); | |
case SetupState.USERNAME_VALIDATION: | |
return ( | |
<UsernameForm | |
username={state.data.username} | |
onUsernameChange={(username) => | |
dispatch({ type: 'SET_USERNAME', payload: username }) | |
} | |
onSubmit={handleUsernameSubmit} | |
/> | |
); | |
case SetupState.AVATAR_UPLOAD: | |
return <AvatarUpload onUpload={handleAvatarUpload} />; | |
case SetupState.BIO_ENTRY: | |
return ( | |
<BioForm | |
bio={state.data.bio || ''} | |
onBioChange={(bio) => dispatch({ type: 'SET_BIO', payload: bio })} | |
onSubmit={handleBioSubmit} | |
/> | |
); | |
case SetupState.COMPLETE: | |
return <button onClick={handleSubmitAll}>Complete Setup</button>; | |
} | |
}; | |
return <div className="profile-setup">{renderCurrentStep()}</div>; | |
}; | |
// Mock functions | |
const validateEmail = async (email: string): Promise<boolean> => true; | |
const checkUsername = async (username: string): Promise<boolean> => true; | |
const submitToServer = (userData: UserData): void => {}; |
The AI sometimes misses important details. For example, it struggled with TypeScript's type guards in my recent refactoring. I had a cashTag variable (string or null) with specific states guaranteeing non-null values.
The AI's initial refactoring broke these guarantees.
So here is what I do instead:
This process works about half the time - not perfect, but valuable given the task complexity.
This particular refactoring would typically take me 5-10 minutes with careful attention. Using AI, I completed the same task in about 30 seconds, plus 1-2 minutes for fixing type errors. Even with some manual fixes needed, the time savings are substantial.
AI tools like Cursor are reshaping both our speed and approach to software development problems. They complement human judgment with fast prototyping and refactoring capabilities.
Looking ahead, I wonder how these tools will evolve to better understand our code's implicit guarantees and patterns. The AI-assisted development journey is just beginning, and its potential is exciting.