PluginBench
Skill
Review
Audit score 70

react-modernization

wshobson/agents

Upgrade React apps to latest versions, migrate class components to hooks, and adopt concurrent features.

What is react-modernization?

Modernize React codebases by upgrading versions, converting class components to functional hooks, and leveraging React 18+ concurrent features like Suspense and transitions. Use this when refactoring legacy React code, adopting hooks patterns, or upgrading to the latest React release.

  • Upgrade React applications across major versions (16→17→18) with breaking change guidance
  • Migrate class components to functional components using hooks (useState, useEffect, useContext)
  • Convert lifecycle methods to useEffect patterns with proper cleanup and dependency management
  • Adopt React 18 concurrent features including automatic batching, transitions, and Suspense
  • Replace HOCs and render props with custom hooks for cleaner code composition
  • Update root API and JSX transform patterns for modern React

How to install react-modernization

npx skills add https://github.com/wshobson/agents --skill react-modernization
Claude Code
Cursor
Windsurf
Cline

How to use react-modernization

  1. 1.Review the version upgrade path for your current React version to understand breaking changes
  2. 2.Identify class components in your codebase to prioritize for migration
  3. 3.Start with state management: convert constructor and setState to useState hooks
  4. 4.Convert lifecycle methods (componentDidMount, componentDidUpdate, componentWillUnmount) to useEffect with proper dependencies
  5. 5.Replace Context.Consumer patterns and HOCs with useContext and custom hooks
  6. 6.Update your root API call from ReactDOM.render to createRoot for React 18
  7. 7.Test automatic batching behavior and use flushSync only where synchronous updates are critical
  8. 8.Implement useTransition for non-urgent state updates and Suspense for data fetching

Use cases

Good for
  • Upgrading a React 16 application to React 18 with automated breaking change fixes
  • Converting a legacy class-based component library to hooks for better maintainability
  • Implementing useTransition for non-blocking UI updates in search or filter interfaces
  • Migrating context consumers and HOCs to useContext and custom hooks
  • Adding Suspense boundaries for progressive data loading in multi-section pages
Who it's for
  • React developers maintaining legacy codebases
  • Teams modernizing applications to React 18+
  • Engineers migrating from class components to hooks
  • Developers adopting concurrent rendering patterns

react-modernization FAQ

What are the main breaking changes when upgrading from React 17 to 18?

React 18 introduces automatic batching (all updates batched, even in async), Strict Mode double invocation for detecting side effects, a new createRoot API replacing ReactDOM.render, and Suspense support on the server. Event delegation and JSX transform changes from React 17 also apply.

How do I convert a class component with multiple lifecycle methods to hooks?

Map componentDidMount and componentDidUpdate to useEffect with appropriate dependencies, componentWillUnmount cleanup to the return function of useEffect, and this.state/this.setState to useState. Use useCallback for stable function references if needed.

When should I use useTransition vs. regular state updates?

Use useTransition for non-urgent updates that can be interrupted (like search results or filtering). Wrap the setState call in startTransition(). Regular state updates remain urgent and update immediately, like input field changes.

What is the difference between Suspense and loading states?

Suspense declaratively handles loading states at the component level with fallback UI, automatically coordinating multiple async operations. Traditional loading states require manual boolean flags and conditional rendering in each component.

Do I need to update all components at once or can I migrate gradually?

You can migrate gradually. Hooks and class components coexist in React, so you can convert components incrementally. However, ensure consistent patterns within a component tree for maintainability.

Full instructions (SKILL.md)

Source of truth, from wshobson/agents.


name: react-modernization description: Upgrade React applications to latest versions, migrate from class components to hooks, and adopt concurrent features. Use when modernizing React codebases, migrating to React Hooks, or upgrading to latest React versions.

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

Transitions

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.