Class to hooks
Skill almasumdev/awesome-react-native-agent-skills/.github/skills/migration/class-to-hooks
Curated agent skills, conventions, and workflows for building React Native apps with AI coding agents.
npx -y skills add almasumdev/awesome-react-native-agent-skills --skill class-to-hooksAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Guidance on migrating legacy React Native class components to function components with hooks. Use when asked to modernize, refactor, or remove class components.
SKILL.md
5.0 KB, as published. Nobody here has run it
Migrating Class Components to Hooks
Instructions
All new components must be function components. When modernizing a class component, apply a predictable mapping from lifecycle methods to hooks.
1. Lifecycle Mapping
| Class method | Hook replacement |
|---|---|
constructor + state | useState / useReducer |
componentDidMount | useEffect(() => { ... }, []) |
componentDidUpdate(prevProps, prevState) | useEffect(() => { ... }, [deps]) |
componentWillUnmount | Cleanup function returned from useEffect |
shouldComponentUpdate | React.memo + stable props, or useMemo |
getDerivedStateFromProps | Derive during render; avoid state duplication |
componentDidCatch | react-error-boundary or a small class wrapper (one of the rare remaining class uses) |
2. Before: Class Component
import { Component } from 'react';
import { View, Text, ActivityIndicator } from 'react-native';
import { fetchArticle } from '@infrastructure/api';
import type { Article } from '@domain/articles';
type Props = { id: string };
type State = { article: Article | null; error: string | null; loading: boolean };
export class ArticleScreen extends Component<Props, State> {
state: State = { article: null, error: null, loading: true };
private aborter = new AbortController();
componentDidMount() {
this.load();
}
componentDidUpdate(prev: Props) {
if (prev.id !== this.props.id) {
this.aborter.abort();
this.aborter = new AbortController();
this.setState({ loading: true, article: null, error: null });
this.load();
}
}
componentWillUnmount() {
this.aborter.abort();
}
private async load() {
try {
const article = await fetchArticle(this.props.id, this.aborter.signal);
this.setState({ article, loading: false });
} catch (e) {
this.setState({ error: String(e), loading: false });
}
}
render() {
if (this.state.loading) return <ActivityIndicator />;
if (this.state.error) return <Text>{this.state.error}</Text>;
return (
<View>
<Text>{this.state.article?.title}</Text>
</View>
);
}
}
3. After: Function Component + Hooks
import { useEffect, useState } from 'react';
import { View, Text, ActivityIndicator } from 'react-native';
import { fetchArticle } from '@infrastructure/api';
import type { Article } from '@domain/articles';
export function ArticleScreen({ id }: { id: string }) {
const [article, setArticle] = useState<Article | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const ac = new AbortController();
setLoading(true);
setError(null);
setArticle(null);
fetchArticle(id, ac.signal)
.then(setArticle)
.catch((e: unknown) => !ac.signal.aborted && setError(String(e)))
.finally(() => !ac.signal.aborted && setLoading(false));
return () => ac.abort();
}, [id]);
if (loading) return <ActivityIndicator />;
if (error) return <Text>{error}</Text>;
return (
<View>
<Text>{article?.title}</Text>
</View>
);
}
4. Even Better: Delegate to TanStack Query
Once you have removed the class, replace hand-rolled fetching with a reusable hook (see rn-data-fetching):
import { useQuery } from '@tanstack/react-query';
import { fetchArticle } from '@infrastructure/api';
export function useArticle(id: string) {
return useQuery({
queryKey: ['article', id],
queryFn: ({ signal }) => fetchArticle(id, signal),
});
}
5. Common Pitfalls
- Stale closures: any value referenced inside
useEffect,useCallback, oruseMemomust be in the dependency array or derived from a ref. Enablereact-hooks/exhaustive-deps. - Setting state after unmount: always gate
setStateon!signal.abortedor use TanStack Query which handles it. getDerivedStateFromProps: 95% of cases should become derived values computed during render, not new state.refforwarding: useforwardRef+useImperativeHandleonly when you need to expose imperative methods.
6. Error Boundaries
Error boundaries still require a class. Wrap once and reuse:
import { ErrorBoundary } from 'react-error-boundary';
<ErrorBoundary fallbackRender={({ error }) => <Text>{error.message}</Text>}>
<ArticleScreen id={id} />
</ErrorBoundary>;
Checklist
- No new class components in the codebase.
- Every migrated
useEffecthas a dependency array and a cleanup function when needed. - In-flight requests are cancelled with
AbortControlleron unmount and prop change. - Derived values are computed during render, not mirrored into state.
-
react-hooks/exhaustive-depslint rule is enabled and clean. - Error boundaries are provided via
react-error-boundary, not ad-hoccomponentDidCatchclasses.