React Modernization
Seth HobsonCommunityCreated by Seth Hobson
- Skill
- Guidelines
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The react-modernization skill provides a structured approach to evolving legacy React codebases. It covers the technical requirements for upgrading between major versions (16 through 18), strategies for refactoring class components into functional components using hooks, and implementation patterns for modern React features like concurrent rendering, Suspense, and automatic batching. The skill also includes guidance on using codemods for automated refactoring and best practices for TypeScript migration.
What it does
- Provides migration paths for upgrading React versions 16 through 18.
- Demonstrates refactoring patterns from class components to functional components with hooks.
- Explains the implementation of React 18 concurrent features like transitions and Suspense.
- Offers guidance on using codemods to automate large-scale code transformations.
- Includes patterns for performance optimization using memoization and code splitting.
- Provides a checklist for managing the migration process safely.
When to use it
- When upgrading an existing React application to the latest major version.
- When refactoring legacy class-based components to modern functional components.
- When adopting React 18 features such as concurrent rendering or the new root API.
- When standardizing state management or lifecycle logic using custom hooks.
- When migrating a JavaScript codebase to TypeScript.
Advantages
- Reduces manual effort through automated codemod recommendations.
- Provides clear before-and-after code comparisons for complex refactoring tasks.
- Ensures architectural consistency by following established React patterns.
- Helps identify and mitigate breaking changes during version upgrades.
Usage examples
- “How do I convert a class component using componentDidMount to a functional component with hooks?”
- The skill provides a side-by-side comparison showing how to replace lifecycle methods with the useEffect hook, including cleanup logic and dependency array management.
- “What are the steps to upgrade my app to React 18?”
- The skill outlines the migration checklist, including updating the root API to createRoot and testing for strict mode double-invocations.
- “Can you help me automate the renaming of unsafe lifecycle methods?”
- The skill provides the specific jscodeshift command and transform file reference to automatically update legacy lifecycle methods across your codebase.
- “How do I implement code splitting in my React app?”
- The skill provides a pattern using React.lazy and Suspense to dynamically load components, improving initial bundle performance.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/wshobson/react-modernization/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip react-modernization.zip -d .agents/skills
Documentation by Seth Hobson
React Modernization
Master React version upgrades, class to hooks migration, concurrent features adoption, and codemods for automated transformation.
When to Use This Skill
Upgrading React applications to latest versions
Migrating class components to functional components with hooks
Adopting concurrent React features (Suspense, transitions)
Applying codemods for automated refactoring
Modernizing state management patterns
Updating to TypeScript
Improving performance with React 18+ features
Version Upgrade Path
React 16 → 17 → 18
Breaking Changes by Version:
React 17:
Event delegation changes
No event pooling
Effect cleanup timing
JSX transform (no React import needed)
React 18:
Automatic batching
Concurrent rendering
Strict Mode changes (double invocation)
New root API
Suspense on server
Class to Hooks Migration
State Management
// Before: Class component
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
name: "",
};
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
// After: Functional component with hooks
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState("");
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}Lifecycle Methods to Hooks
// Before: Lifecycle methods
class DataFetcher extends React.Component {
state = { data: null, loading: true };
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
if (prevProps.id !== this.props.id) {
this.fetchData();
}
}
componentWillUnmount() {
this.cancelRequest();
}
fetchData = async () => {
const data = await fetch(`/api/${this.props.id}`);
this.setState({ data, loading: false });
};
cancelRequest = () => {
// Cleanup
};
render() {
if (this.state.loading) return <div>Loading...</div>;
return <div>{this.state.data}</div>;
}
}
// After: useEffect hook
function DataFetcher({ id }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
const fetchData = async () => {
try {
const response = await fetch(`/api/${id}`);
const result = await response.json();
if (!cancelled) {
setData(result);
setLoading(false);
}
} catch (error) {
if (!cancelled) {
console.error(error);
}
}
};
fetchData();
// Cleanup function
return () => {
cancelled = true;
};
}, [id]); // Re-run when id changes
if (loading) return <div>Loading...</div>;
return <div>{data}</div>;
}Context and HOCs to Hooks
// Before: Context consumer and HOC
const ThemeContext = React.createContext();
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
return (
<button style={{ background: this.context.theme }}>
{this.props.children}
</button>
);
}
}
// After: useContext hook
function ThemedButton({ children }) {
const { theme } = useContext(ThemeContext);
return <button style={{ background: theme }}>{children}</button>;
}
// Before: HOC for data fetching
function withUser(Component) {
return class extends React.Component {
state = { user: null };
componentDidMount() {
fetchUser().then((user) => this.setState({ user }));
}
render() {
return <Component {...this.props} user={this.state.user} />;
}
};
}
// After: Custom hook
function useUser() {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser().then(setUser);
}, []);
return user;
}
function UserProfile() {
const user = useUser();
if (!user) return <div>Loading...</div>;
return <div>{user.name}</div>;
}React 18 Concurrent Features
New Root API
// Before: React 17
import ReactDOM from "react-dom";
ReactDOM.render(<App />, document.getElementById("root"));
// After: React 18
import { createRoot } from "react-dom/client";
const root = createRoot(document.getElementById("root"));
root.render(<App />);Automatic Batching
// React 18: All updates are batched
function handleClick() {
setCount((c) => c + 1);
setFlag((f) => !f);
// Only one re-render (batched)
}
// Even in async:
setTimeout(() => {
setCount((c) => c + 1);
setFlag((f) => !f);
// Still batched in React 18!
}, 1000);
// Opt out if needed
import { flushSync } from "react-dom";
flushSync(() => {
setCount((c) => c + 1);
});
// Re-render happens here
setFlag((f) => !f);
// Another re-renderTransitions
import { useState, useTransition } from "react";
function SearchResults() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
// Urgent: Update input immediately
setQuery(e.target.value);
// Non-urgent: Update results (can be interrupted)
startTransition(() => {
setResults(searchResults(e.target.value));
});
};
return (
<>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<Results data={results} />
</>
);
}Suspense for Data Fetching
import { Suspense } from "react";
// Resource-based data fetching (with React 18)
const resource = fetchProfileData();
function ProfilePage() {
return (
<Suspense fallback={<Loading />}>
<ProfileDetails />
<Suspense fallback={<Loading />}>
<ProfileTimeline />
</Suspense>
</Suspense>
);
}
function ProfileDetails() {
// This will suspend if data not ready
const user = resource.user.read();
return <h1>{user.name}</h1>;
}
function ProfileTimeline() {
const posts = resource.posts.read();
return <Timeline posts={posts} />;
}Additional patterns and templates
More detailed templates and worked examples live in references/details.md. Read that file for the full pattern library.
Files
- ATTRIBUTION.mdAdded by AutoGineer995 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md6.7 KiB
- references/details.md4.3 KiB
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Seth Hobson
- Publisher
- Seth Hobson (Community)
- Source
- wshobson/agents · plugins/framework-migration/skills/react-modernization · commit 4bde316
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Seth Hobson or Seth Hobson.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download react-modernization.zip