react-modernization
SKILL.md
React Modernization
Guide for upgrading React applications, migrating patterns, and adopting modern features.
When to Use
- Upgrading React to 18.x or 19.x
- Migrating class components to function components with hooks
- Adopting concurrent features (Suspense, Transitions, use())
- Running codemods for automated transformations
- Modernizing legacy patterns (HOCs to hooks, lifecycle to effects)
Upgrade Path
React 16/17 ──> React 18 ──> Adopt Concurrent Features ──> React 19
│ │ │
createRoot migration Suspense + Transitions use() + Actions
Automatic batching useDeferredValue useOptimistic
StrictMode changes Error Boundaries React Compiler
React 18 Migration
Breaking Changes
ReactDOM.renderreplaced bycreateRoot(required)- Automatic batching in all contexts (may change behavior)
- Strict Mode double-renders effects in dev
Install & Codemod
npm install react@18 react-dom@18 @types/react@18 @types/react-dom@18
npx codemod react/19/replace-reactdom-render
// BEFORE (React 17)
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, document.getElementById('root'));
// AFTER (React 18+)
import { createRoot } from 'react-dom/client';
createRoot(document.getElementById('root')!).render(<App />);
TypeScript Changes
React.FCno longer includeschildren— add explicitly to propsReact.VFCremoved — useReact.FC- Stricter generics on
useCallback/useMemo
Class to Function Component Migration
Priority Order
- Leaf components (no children, simple props) — easiest wins
- Container components (state + data fetching)
- Higher-Order Components — extract to custom hooks
- Ref-forwarding components — useRef + forwardRef
- Error Boundaries — keep as class (no hook equivalent)
Lifecycle to Hooks Mapping
| Class Lifecycle | Hook Equivalent |
|---|---|
constructor / state init |
useState(initialValue) |
componentDidMount |
useEffect(() => { ... }, []) |
componentDidUpdate |
useEffect(() => { ... }, [deps]) |
componentWillUnmount |
useEffect return cleanup |
shouldComponentUpdate |
React.memo(Component) |
getDerivedStateFromProps |
Compute during render or useMemo |
componentDidCatch |
No hook — keep as class Error Boundary |
Migration Example
// BEFORE: Class
class UserProfile extends React.Component<Props, State> {
state = { user: null, loading: true };
componentDidMount() {
fetchUser(this.props.id).then(user =>
this.setState({ user, loading: false })
);
}
componentDidUpdate(prev: Props) {
if (prev.id !== this.props.id) {
this.setState({ loading: true });
fetchUser(this.props.id).then(user =>
this.setState({ user, loading: false })
);
}
}
render() {
if (this.state.loading) return <Spinner />;
return <div>{this.state.user?.name}</div>;
}
}
// AFTER: Function + hooks
function UserProfile({ id }: Props) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetchUser(id).then(u => { setUser(u); setLoading(false); });
}, [id]);
if (loading) return <Spinner />;
return <div>{user?.name}</div>;
}
HOC to Custom Hook
// BEFORE: HOC
function withAuth(Component) {
return (props) => {
const user = useContext(AuthContext);
if (!user) return <Redirect to="/login" />;
return <Component {...props} user={user} />;
};
}
// AFTER: Custom hook
function useAuth() {
const user = useContext(AuthContext);
return { user, isAuthenticated: !!user };
}
Concurrent Features (React 18+)
Suspense Boundaries
<Suspense fallback={<Skeleton />}>
<LazyComponent />
</Suspense>
useTransition — Non-Urgent Updates
const [isPending, startTransition] = useTransition();
function handleSearch(value: string) {
setQuery(value); // Urgent: update input
startTransition(() => setResults(filter(value))); // Deferred: filter
}
useDeferredValue — Defer Expensive Renders
const deferredQuery = useDeferredValue(query);
const results = useMemo(() => search(deferredQuery), [deferredQuery]);
React 19 Features
use() — Read Promises and Context
// Suspends until promise resolves
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return <div>{user.name}</div>;
}
// Conditional context reading (new in 19)
function Theme({ show }: { show: boolean }) {
if (!show) return null;
const theme = use(ThemeContext);
return <div style={{ color: theme.primary }}>Themed</div>;
}
useActionState — Form Actions
const [state, formAction, isPending] = useActionState(
async (prev, formData) => {
const result = await login(formData);
if (result.error) return { error: result.error };
redirect('/dashboard');
},
{ error: null }
);
return (
<form action={formAction}>
<input name="email" type="email" />
<button disabled={isPending}>
{isPending ? 'Signing in...' : 'Sign In'}
</button>
</form>
);
useOptimistic — Optimistic UI
const [optimisticTodos, addOptimistic] = useOptimistic(
todos,
(state, newTodo: Todo) => [...state, newTodo]
);
async function addTodo(formData: FormData) {
const todo = { id: crypto.randomUUID(), text: formData.get('text') as string };
addOptimistic(todo); // Instant UI update
await saveTodo(todo); // Server call
}
Codemods
# All React 19 codemods at once
npx codemod@latest react/19/migration-recipe
# Individual codemods
npx codemod react/19/replace-reactdom-render # createRoot
npx codemod react/19/replace-string-ref # string refs to useRef
npx codemod react/19/replace-act-import # act() import path
npx codemod react/19/replace-use-form-state # useFormState to useActionState
Migration Checklist
- Update react, react-dom, and @types packages
- Replace ReactDOM.render with createRoot
- Run codemods for automated fixes
- Fix TypeScript errors (FC children, etc.)
- Test with StrictMode enabled (double-render effects)
- Migrate class components (leaf first, then containers)
- Replace HOCs with custom hooks
- Add Suspense boundaries for code splitting
- Adopt useTransition for non-urgent updates
- Keep Error Boundaries as class components
- Test all forms and user flows end-to-end
Weekly Installs
4
Repository
sivag-lab/roth_mcpGitHub Stars
1
First Seen
3 days ago
Security Audits
Installed on
opencode4
gemini-cli4
claude-code4
github-copilot4
codex4
kimi-cli4