The React Ecosystem in 2023: A Map for the Overwhelmed

SS Saurav Sitaula

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 useState if 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)

  • useReducer for complex state
  • Custom hooks patterns
  • Suspense for data fetching
  • Server Components
  • useTransition / useDeferredValue

Don’t Stress About (Yet)

  • The React Compiler
  • useSyncExternalStore
  • useInsertionEffect
  • 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.

SS

Saurav Sitaula

Software Architect • Nepal

Back to all posts
Saurav.dev

© 2026 Saurav Sitaula.AstroNeoBrutalism