🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

React Modernization

by @wpank

Upgrade React apps by migrating class components to hooks, adopting React 18/19 concurrent features, running codemods, and adding TypeScript types.

Versionv1.0.0
Downloads1,581
Installs5
TERMINAL
clawhub install react-modernization

πŸ“– About This Skill


name: react-modernization model: reasoning

React Modernization

Upgrade React applications from class components to hooks, adopt concurrent features, and migrate between major versions.

WHAT

Systematic patterns for modernizing React codebases:

  • Class-to-hooks migration with lifecycle method mappings
  • React 18/19 concurrent features adoption
  • TypeScript migration for React components
  • Automated codemods for bulk refactoring
  • Performance optimization with modern APIs
  • WHEN

  • Migrating class components to functional components with hooks
  • Upgrading React 16/17 apps to React 18/19
  • Adopting concurrent features (Suspense, transitions, use)
  • Converting HOCs and render props to custom hooks
  • Adding TypeScript to React projects
  • KEYWORDS

    react upgrade, class to hooks, useEffect, useState, react 18, react 19, concurrent, suspense, transition, codemod, migrate, modernize, functional component

    Installation

    OpenClaw / Moltbot / Clawbot

    npx clawhub@latest install react-modernization
    


    Version Upgrade Paths

    React 17 β†’ 18 Breaking Changes

    | Change | Impact | Migration | |--------|--------|-----------| | New root API | Required | ReactDOM.render β†’ createRoot | | Automatic batching | Behavior | State updates batch in async code now | | Strict Mode | Dev only | Effects fire twice (mount/unmount/mount) | | Suspense on server | Optional | Enable SSR streaming |

    React 18 β†’ 19 Breaking Changes

    | Change | Impact | Migration | |--------|--------|-----------| | use() hook | New API | Read promises/context in render | | ref as prop | Simplified | No more forwardRef needed | | Context as provider | Simplified | not | | Async actions | New pattern | useActionState, useOptimistic |


    Class to Hooks Migration

    Lifecycle Method Mappings

    // componentDidMount β†’ useEffect with empty deps
    useEffect(() => {
      fetchData()
    }, [])

    // componentDidUpdate β†’ useEffect with deps useEffect(() => { updateWhenIdChanges() }, [id])

    // componentWillUnmount β†’ useEffect cleanup useEffect(() => { const subscription = subscribe() return () => subscription.unsubscribe() }, [])

    // shouldComponentUpdate β†’ React.memo const Component = React.memo(({ data }) =>

    {data}
    )

    // getDerivedStateFromProps β†’ useMemo const derivedValue = useMemo(() => computeFrom(props), [props])

    State Migration Pattern

    // BEFORE: Class with multiple state properties
    class UserProfile extends React.Component {
      state = { user: null, loading: true, error: null }
      
      componentDidMount() {
        fetchUser(this.props.id)
          .then(user => this.setState({ user, loading: false }))
          .catch(error => this.setState({ error, loading: false }))
      }
      
      componentDidUpdate(prevProps) {
        if (prevProps.id !== this.props.id) {
          this.setState({ loading: true })
          fetchUser(this.props.id)
            .then(user => this.setState({ user, loading: false }))
        }
      }
      
      render() {
        const { user, loading, error } = this.state
        if (loading) return 
        if (error) return 
        return 
      }
    }

    // AFTER: Custom hook + functional component function useUser(id: string) { const [user, setUser] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null)

    useEffect(() => { let cancelled = false setLoading(true) fetchUser(id) .then(data => { if (!cancelled) { setUser(data) setLoading(false) } }) .catch(err => { if (!cancelled) { setError(err) setLoading(false) } })

    return () => { cancelled = true } }, [id])

    return { user, loading, error } }

    function UserProfile({ id }: { id: string }) { const { user, loading, error } = useUser(id) if (loading) return if (error) return return }

    HOC to Hook Migration

    // BEFORE: Higher-Order Component
    function withUser(Component) {
      return function WithUser(props) {
        const [user, setUser] = useState(null)
        useEffect(() => { fetchUser().then(setUser) }, [])
        return 
      }
    }

    const ProfileWithUser = withUser(Profile)

    // AFTER: Custom hook (simpler, composable) function useCurrentUser() { const [user, setUser] = useState(null) useEffect(() => { fetchUser().then(setUser) }, []) return user }

    function Profile() { const user = useCurrentUser() return user ?

    {user.name}
    : null }


    React 18+ Concurrent Features

    New Root API (Required)

    // BEFORE: React 17
    import ReactDOM from 'react-dom'
    ReactDOM.render(, document.getElementById('root'))

    // AFTER: React 18+ import { createRoot } from 'react-dom/client' const root = createRoot(document.getElementById('root')!) root.render()

    useTransition for Non-Urgent Updates

    function SearchResults() {
      const [query, setQuery] = useState('')
      const [results, setResults] = useState([])
      const [isPending, startTransition] = useTransition()

    function handleChange(e: React.ChangeEvent) { // Urgent: update input immediately setQuery(e.target.value) // Non-urgent: can be interrupted startTransition(() => { setResults(searchDatabase(e.target.value)) }) }

    return ( <> {isPending ? : } ) }

    Suspense for Data Fetching

    // With React 19's use() hook
    function ProfilePage({ userId }: { userId: string }) {
      return (
        }>
          
        
      )
    }

    function ProfileDetails({ userId }: { userId: string }) { // use() suspends until promise resolves const user = use(fetchUser(userId)) return

    {user.name}

    }

    React 19: use() Hook

    // Read promises directly in render
    function Comments({ commentsPromise }) {
      const comments = use(commentsPromise)
      return comments.map(c => )
    }

    // Read context (simpler than useContext) function ThemeButton() { const theme = use(ThemeContext) return }

    React 19: Actions

    // useActionState for form submissions
    function UpdateName() {
      const [error, submitAction, isPending] = useActionState(
        async (previousState, formData) => {
          const error = await updateName(formData.get('name'))
          if (error) return error
          redirect('/profile')
        },
        null
      )

    return (

    {error &&

    {error}

    }
    ) }


    Automated Codemods

    Run Official React Codemods

    # Update to new JSX transform (no React import needed)
    npx codemod@latest react/19/replace-reactdom-render

    Update deprecated APIs

    npx codemod@latest react/19/replace-string-ref

    Class to function components

    npx codemod@latest react/19/replace-use-form-state

    Manual Search Patterns

    # Find class components
    rg "class \w+ extends (React\.)?Component" --type tsx

    Find deprecated lifecycle methods

    rg "componentWillMount|componentWillReceiveProps|componentWillUpdate" --type tsx

    Find ReactDOM.render (needs migration to createRoot)

    rg "ReactDOM\.render" --type tsx


    TypeScript Migration

    // Add types to functional components
    interface ButtonProps {
      onClick: () => void
      children: React.ReactNode
      variant?: 'primary' | 'secondary'
    }

    function Button({ onClick, children, variant = 'primary' }: ButtonProps) { return ( ) }

    // Type event handlers function Form() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault() } const handleChange = (e: React.ChangeEvent) => { console.log(e.target.value) }

    return (

    ) }

    // Generic components interface ListProps { items: T[] renderItem: (item: T) => React.ReactNode }

    function List({ items, renderItem }: ListProps) { return <>{items.map(renderItem)} }


    Migration Checklist

    Pre-Migration

  • [ ] Upgrade dependencies incrementally
  • [ ] Review breaking changes in release notes
  • [ ] Set up comprehensive test coverage
  • [ ] Create feature branch
  • Class β†’ Hooks

  • [ ] Start with leaf components (no children)
  • [ ] Convert state to useState
  • [ ] Convert lifecycle to useEffect
  • [ ] Extract shared logic to custom hooks
  • [ ] Convert HOCs to hooks where possible
  • React 18+ Upgrade

  • [ ] Update to createRoot API
  • [ ] Test with StrictMode double-invocation
  • [ ] Address hydration mismatches
  • [ ] Adopt Suspense boundaries where beneficial
  • [ ] Use transitions for expensive updates
  • Post-Migration

  • [ ] Run full test suite
  • [ ] Check for console warnings
  • [ ] Profile performance before/after
  • [ ] Document changes for team

  • NEVER

  • Skip testing after migration
  • Migrate multiple components in one commit
  • Ignore StrictMode warnings (they reveal bugs)
  • Use // eslint-disable-next-line react-hooks/exhaustive-deps without understanding why
  • Mix class and hooks in same component