Refactoring a messy React component using AI
Modern React suggests avoiding class-based React components, instead recommending the newer functional syntax. Below is a simple form written using class-based syntax that contains a few opportunities for refactoring.
Exercise
Section titled “Exercise”The form is a simple login that includes email and password fields, some client-side validation logic, and a success message if the form submits successfully. However, it also includes a few common code smells, such as duplicated handlers and tightly coupled state. I decided to give it to a GPT-4-class language model to refactor the component to see how it would do:
import React, { Component } from "react";
class SignupForm extends Component { constructor(props) { super(props); this.state = { email: "", password: "", confirmPassword: "", error: "", submitted: false, loading: false };
this.handleEmail = this.handleEmail.bind(this); this.handlePassword = this.handlePassword.bind(this); this.handleConfirmPassword = this.handleConfirmPassword.bind(this); this.submitForm = this.submitForm.bind(this); }
handleEmail(e) { this.setState({ email: e.target.value }); }
handlePassword(e) { this.setState({ password: e.target.value }); }
handleConfirmPassword(e) { this.setState({ confirmPassword: e.target.value }); }
submitForm(e) { e.preventDefault(); this.setState({ loading: true, error: "" });
if (!this.state.email.includes("@")) { this.setState({ error: "Invalid email", loading: false }); return; } if (this.state.password !== this.state.confirmPassword) { this.setState({ error: "Passwords do not match", loading: false }); return; }
setTimeout(() => { this.setState({ submitted: true, loading: false }); }, 1200); }
render() { return ( <form onSubmit={this.submitForm}> <input type="text" placeholder="Email" value={this.state.email} onChange={this.handleEmail} /> <input type="password" placeholder="Password" value={this.state.password} onChange={this.handlePassword} /> <input type="password" placeholder="Confirm Password" value={this.state.confirmPassword} onChange={this.handleConfirmPassword} /> <button type="submit" disabled={this.state.loading}>Sign Up</button> {this.state.error && <p style={{ color: "red" }}>{this.state.error}</p>} {this.state.submitted && <p>Success!</p>} </form> ); }}
export default SignupForm;The LLM was able to do a fine job of refactoring the component. It not only implemented the more modern functional paradigm, but also ‘DRY’-ed up some of the form handling logic so that it can handle additional fields if added, which aligns with the spirit of the Open/Closed Principle of SOLID by allowing by allowing the component to be extended without modifying core logic:
import { useState, useCallback } from "react";
const initialFormState = { email: "", password: "", confirmPassword: ""};
export default function SignupForm() { const [form, setForm] = useState(initialFormState); const [error, setError] = useState(""); const [submitted, setSubmitted] = useState(false); const [loading, setLoading] = useState(false);
const updateField = useCallback((e) => { const { name, value } = e.target; setForm((prev) => ({ ...prev, [name]: value })); }, []);
const validate = () => { if (!form.email.includes("@")) { return "Invalid email"; } if (form.password !== form.confirmPassword) { return "Passwords do not match"; } return ""; };
const submitForm = (e) => { e.preventDefault(); setError(""); setLoading(true);
const validationError = validate(); if (validationError) { setError(validationError); setLoading(false); return; }
setTimeout(() => { setSubmitted(true); setLoading(false); }, 1200); };
return ( <form onSubmit={submitForm}> <input name="email" type="text" placeholder="Email" value={form.email} onChange={updateField} />
<input name="password" type="password" placeholder="Password" value={form.password} onChange={updateField} />
<input name="confirmPassword" type="password" placeholder="Confirm Password" value={form.confirmPassword} onChange={updateField} />
<button type="submit" disabled={loading}> {loading ? "Signing up..." : "Sign Up"} </button>
{error && <p style={{ color: "red" }}>{error}</p>} {submitted && <p>Success!</p>} </form> );}What the AI did well
Section titled “What the AI did well”What’s addressed successfully here is that the refactor not only converts the component to a functional one, achieving the goal of the exercise, but also improves its overall structure.
This version cleans up the code so that
- it’s readable
- separates form handling into reusable logic
- introduces a validation function with a single responsibility
- and removes complexity without changing user-facing behavior
Where the AI Fell Short
Section titled “Where the AI Fell Short”While the model successfully modernized the component and reduced duplication, it didn’t address
- accessibility
- options for improvements to the existing validation
- error handling
- and testing
These omissions are typical of AI-assisted refactors: they preserve intent and structure well, but still require human review to bring a project over the finish line when ensuring correctness, usability, and long-term maintainability.