react-modernization — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited react-modernization (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Upgrade React applications from class components to hooks, adopt concurrent features, and migrate between major versions.
Systematic patterns for modernizing React codebases:
react upgrade, class to hooks, useEffect, useState, react 18, react 19, concurrent, suspense, transition, codemod, migrate, modernize, functional component
npx clawhub@latest install react-modernization| 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 |
| Change | Impact | Migration |
|---|---|---|
use() hook | New API | Read promises/context in render |
ref as prop | Simplified | No more forwardRef needed |
| Context as provider | Simplified | <Context> not <Context.Provider> |
| Async actions | New pattern | useActionState, useOptimistic |
// 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 }) => <div>{data}</div>)
// getDerivedStateFromProps → useMemo
const derivedValue = useMemo(() => computeFrom(props), [props])// 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 <Spinner />
if (error) return <Error message={error.message} />
return <Profile user={user} />
}
}
// AFTER: Custom hook + functional component
function useUser(id: string) {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error | null>(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 <Spinner />
if (error) return <Error message={error.message} />
return <Profile user={user} />
}// BEFORE: Higher-Order Component
function withUser(Component) {
return function WithUser(props) {
const [user, setUser] = useState(null)
useEffect(() => { fetchUser().then(setUser) }, [])
return <Component {...props} user={user} />
}
}
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 ? <div>{user.name}</div> : null
}// 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 />)function SearchResults() {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [isPending, startTransition] = useTransition()
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
// Urgent: update input immediately
setQuery(e.target.value)
// Non-urgent: can be interrupted
startTransition(() => {
setResults(searchDatabase(e.target.value))
})
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending ? <Spinner /> : <ResultsList data={results} />}
</>
)
}// With React 19's use() hook
function ProfilePage({ userId }: { userId: string }) {
return (
<Suspense fallback={<ProfileSkeleton />}>
<ProfileDetails userId={userId} />
</Suspense>
)
}
function ProfileDetails({ userId }: { userId: string }) {
// use() suspends until promise resolves
const user = use(fetchUser(userId))
return <h1>{user.name}</h1>
}// Read promises directly in render
function Comments({ commentsPromise }) {
const comments = use(commentsPromise)
return comments.map(c => <Comment key={c.id} {...c} />)
}
// Read context (simpler than useContext)
function ThemeButton() {
const theme = use(ThemeContext)
return <button className={theme}>Click</button>
}// 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 (
<form action={submitAction}>
<input name="name" />
<button disabled={isPending}>Update</button>
{error && <p>{error}</p>}
</form>
)
}# 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# 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// Add types to functional components
interface ButtonProps {
onClick: () => void
children: React.ReactNode
variant?: 'primary' | 'secondary'
}
function Button({ onClick, children, variant = 'primary' }: ButtonProps) {
return (
<button onClick={onClick} className={variant}>
{children}
</button>
)
}
// Type event handlers
function Form() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
}
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value)
}
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} />
</form>
)
}
// Generic components
interface ListProps<T> {
items: T[]
renderItem: (item: T) => React.ReactNode
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <>{items.map(renderItem)}</>
}useStateuseEffectcreateRoot API// eslint-disable-next-line react-hooks/exhaustive-deps without understanding why~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.