Skip to content

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.

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’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

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.