The React Ecosystem in 2023: A Map for the Overwhelmed
After four years of React, the ecosystem felt both familiar and completely different. React 18 brought concurrent features. The React Compiler was announced. Server Components changed the game. Frameworks battled for dominance. Here's my honest map of where everything stands and what actually matters.
Four Years and Fourteen Blog Posts Later
I started this journey in 2019 with “What The Heck Is A Component?”. Copy-pasting HTML navigation bars. Confused by JSX. Terrified of this.
Four years later, I’ve covered components, state, lifecycles, hooks, custom hooks, context, performance, Redux, Redux Toolkit, modern state management, error boundaries, Suspense, Server Components, the PHP full circle, React Query, and Next.js.
That’s a lot.
And the ecosystem didn’t slow down while I was learning. If anything, it accelerated.
So this post is different. It’s not a deep dive into one concept. It’s a map. Because when you look at the React ecosystem today, it’s easy to feel overwhelmed. I want to help with that.
The Big Picture: React in 2023
Here’s what happened while we were all busy building things:
React 18: The Concurrent Era
React 18 shipped in March 2022 with concurrent features that most people still don’t fully understand (me included, honestly).
What’s actually useful today:
// Automatic batching — multiple state updates = one re-render
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
setName('New name');
// React 17: THREE re-renders
// React 18: ONE re-render (automatic batching)
}
// useTransition — keep the UI responsive during heavy updates
function SearchResults() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
function handleChange(e) {
// Urgent: update the input immediately
setQuery(e.target.value);
// Non-urgent: filter results can wait
startTransition(() => {
setFilteredResults(heavyFilter(e.target.value));
});
}
return (
<>
<input value={query} onChange={handleChange} />
<div style={{ opacity: isPending ? 0.7 : 1 }}>
<ResultsList results={filteredResults} />
</div>
</>
);
}
// useDeferredValue — defer updating a value until urgent work is done
function SearchPage({ query }) {
const deferredQuery = useDeferredValue(query);
return (
<>
<SearchInput query={query} />
<Suspense fallback={<Spinner />}>
<SearchResults query={deferredQuery} /> {/* Updates after input */}
</Suspense>
</>
);
}
What most people skip: useSyncExternalStore, useInsertionEffect, useId. They exist for library authors. You probably don’t need them directly.
The use Hook: React’s Latest Addition
React introduced use — a hook that can read promises and context:
// Reading a promise (works with Suspense)
function Comments({ commentsPromise }) {
const comments = use(commentsPromise);
return comments.map(c => <Comment key={c.id} comment={c} />);
}
// Reading context (works anywhere, even in conditionals!)
function ThemeButton({ showIcon }) {
if (showIcon) {
const theme = use(ThemeContext); // This is legal now!
return <Icon color={theme.primary} />;
}
return null;
}
The use hook breaks one of the original rules of hooks — it can be called conditionally. This is because it’s designed for a different mental model, one where React handles the async flow.
The React Compiler (React Forget)
The React team announced a compiler that automatically memoizes your components. No more manual useMemo, useCallback, or React.memo:
// Before (manual memoization)
function TodoList({ todos, filter }) {
const filteredTodos = useMemo(
() => todos.filter(todo => todo.status === filter),
[todos, filter]
);
const handleToggle = useCallback(
(id) => toggleTodo(id),
[]
);
return filteredTodos.map(todo => (
<MemoizedTodo key={todo.id} todo={todo} onToggle={handleToggle} />
));
}
// After (compiler handles it)
function TodoList({ todos, filter }) {
const filteredTodos = todos.filter(todo => todo.status === filter);
const handleToggle = (id) => toggleTodo(id);
return filteredTodos.map(todo => (
<Todo key={todo.id} todo={todo} onToggle={handleToggle} />
));
}
Write the code that makes sense. The compiler figures out what to memoize. Remember my whole performance optimization post about useMemo and useCallback? The compiler could make most of that advice obsolete.
It’s not shipped yet for everyone, but Instagram is already using it in production.
The Framework Wars
When I started React, there was one way to start: create-react-app. Now it’s been deprecated, and the official React docs recommend using a framework.
Here’s the landscape:
Next.js
I covered this in my last post. The most popular React framework. Built by Vercel.
Choose when: Full-stack apps. E-commerce. Marketing sites. Most projects.
Remix
Built by the React Router team. Focuses on web fundamentals — forms, HTTP, progressive enhancement.
// Remix loader — runs on server
export async function loader({ params }) {
return json(await getPost(params.slug));
}
// Remix action — handles form submissions
export async function action({ request }) {
const formData = await request.formData();
await createComment(Object.fromEntries(formData));
return redirect('/posts');
}
export default function Post() {
const post = useLoaderData();
return (
<article>
<h1>{post.title}</h1>
<Form method="post">
<textarea name="comment" />
<button type="submit">Comment</button>
</Form>
</article>
);
}
Choose when: You value web standards. Heavily form-based apps. You want progressive enhancement.
Astro
Not strictly React, but it supports React components. Ships zero JavaScript by default.
---
// This runs on the server at build time
const posts = await fetchPosts();
---
<html>
<body>
<h1>Blog</h1>
{posts.map(post => <PostCard post={post} />)}
<!-- Only this component ships JavaScript -->
<SearchBar client:load />
</body>
</html>
Choose when: Content-heavy sites. Blogs. Documentation. Marketing pages. (This site you’re reading right now is built with Astro.)
Vite + React (No Framework)
Just React with a fast build tool. No SSR, no routing decisions.
npm create vite@latest my-app -- --template react-ts
Choose when: SPAs that don’t need SSR. Internal tools. Dashboards behind auth. Learning React basics.
The 2023 Stack I’d Recommend
If someone asked me “what should I use for a new React project in 2023?”, here’s my answer:
The Foundation
- Framework: Next.js (or Remix if you’re form-heavy)
- Language: TypeScript (not optional anymore, in my opinion)
- Styling: Tailwind CSS (or CSS Modules if you prefer)
Data Layer
- Server State: TanStack Query (React Query)
- Client State: Zustand (or just
useStateif it’s simple) - Forms: React Hook Form + Zod for validation
Quality
- Linting: ESLint with the recommended configs
- Formatting: Prettier
- Testing: Vitest + Testing Library
Deployment
- Vercel for Next.js (easiest path)
- Cloudflare Pages for static sites
- Docker for self-hosted
What You Actually Need to Learn
Here’s the thing that would have saved me years of anxiety: you don’t need to learn everything.
Must Know (Core React)
- Components and JSX
- Props and State (
useState) - Effects (
useEffect) - Context (
useContext) - Refs (
useRef) - Basic hooks patterns
Should Know (Practical Skills)
- TypeScript with React
- A data fetching library (React Query)
- A framework (Next.js)
- Error boundaries
- Basic performance patterns
Nice to Know (When You Need Them)
useReducerfor complex state- Custom hooks patterns
- Suspense for data fetching
- Server Components
useTransition/useDeferredValue
Don’t Stress About (Yet)
- The React Compiler
useSyncExternalStoreuseInsertionEffect- Every new library that trends on Twitter
The Opinions I’ve Formed
After four years and a lot of production code, here are my honest takes:
TypeScript is not optional. Every any type is a bug waiting to happen. Every untyped prop is a future runtime error. The initial slowdown pays for itself within a month.
Server Components are the future. Not every project needs them today. But the mental model — server for static, client for interactive — is correct.
The best state management is less state. Derive what you can. Fetch what you need. Store only what’s left. Most “state management problems” are “too much state” problems.
Frameworks are not lock-in. They’re leverage. The time you save not configuring webpack is time you spend building features.
The ecosystem is stabilizing. We’re past the “new framework every week” phase. The patterns are solidifying. It’s a good time to be a React developer.
Looking Back, Looking Forward
I started this series confused by what a component was. Now I’m writing about compilers and server-client boundaries.
The learning never stops. But the fundamentals from post #1 still apply: break your UI into small, reusable pieces. That idea hasn’t changed in four years. It won’t change in the next four.
If you’re just starting out, don’t be intimidated by the size of the ecosystem. Start with components. Add state. Learn hooks. Build something. The rest comes when you need it.
And if you’re a seasoned developer feeling overwhelmed by the pace of change — take a breath. The core of React is stable. The ecosystem is maturing. You don’t need to learn everything. You need to learn the right things at the right time.
That’s what this whole series has been about.
P.S. — Someone asked me recently: “If you could start over, what would you learn first?” My answer hasn’t changed since 2019: HTML, CSS, and JavaScript. Not React. Not TypeScript. Not Next.js. The fundamentals. Everything else is built on top of them. The frameworks will change. The web won’t.
Saurav Sitaula
Software Architect • Nepal