Fetching latest headlines…
React in 2026: Start From Scratch the Right Way (+ Cheat Sheet)
NORTH AMERICA
πŸ‡ΊπŸ‡Έ United Statesβ€’April 2, 2026

React in 2026: Start From Scratch the Right Way (+ Cheat Sheet)

20 views0 likes0 comments
Originally published byDev.to

A complete guide to modern React β€” no useEffect abuse, no performance guessing, just clean architecture you'll actually use.

Why This Guide Exists

Most React tutorials are still teaching 2020 patterns. In 2026, the ecosystem has shifted dramatically: React 19 is stable, the compiler handles most memoization automatically, and useEffect should be a last resort β€” not your go-to for data fetching, derived state, or event responses.

This guide walks you from project setup to production-ready patterns, with a cheat sheet you can bookmark.

1. Scaffolding: What to Use in 2026

If you're building a full-stack or SSR app

npx create-next-app@latest my-app --typescript --tailwind --eslint --app

Next.js 15+ with the App Router is the default for production apps. It gives you Server Components, streaming, and partial pre-rendering out of the box.

If you're building a pure client SPA

npm create vite@latest my-app -- --template react-ts

Vite is the standard bundler for SPAs. Fast HMR, zero config, ESM-first.

If you're building something UI-heavy or a design system

npm create remix@latest

Remix is excellent when you care deeply about progressive enhancement and form handling.

2. Folder Structure (Scalable from Day 1)

src/
β”œβ”€β”€ app/               # Pages / routing (Next.js) or routes/
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ ui/            # Generic, reusable UI primitives
β”‚   └── features/      # Feature-specific components
β”œβ”€β”€ hooks/             # Custom hooks
β”œβ”€β”€ lib/               # Utilities, API clients, helpers
β”œβ”€β”€ stores/            # Global state (Zustand, Jotai, etc.)
β”œβ”€β”€ types/             # Shared TypeScript types
└── styles/            # Global CSS / Tailwind config

Rule: Components should not import from pages/ or app/. Data flows down. Side effects live in hooks.

3. React 19 Features You Should Actually Use

use() β€” The New Data Primitive

import { use, Suspense } from 'react';

async function fetchUser(id: string) {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

// In a Server Component or with a cached promise:
function UserCard({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise); // unwraps the promise
  return <div>{user.name}</div>;
}

// Wrap in Suspense at the boundary
<Suspense fallback={<Skeleton />}>
  <UserCard userPromise={fetchUser('123')} />
</Suspense>

useOptimistic β€” Instant UI, Real Sync

import { useOptimistic } from 'react';

function LikeButton({ post }: { post: Post }) {
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    post.likes,
    (currentLikes, increment: number) => currentLikes + increment
  );

  async function handleLike() {
    addOptimisticLike(1);       // instant update
    await likePost(post.id);    // real request in background
  }

  return (
    <button onClick={handleLike}>
      {optimisticLikes} likes
    </button>
  );
}

useActionState β€” Form State Without the Chaos

import { useActionState } from 'react';

async function createPost(prevState: any, formData: FormData) {
  const title = formData.get('title') as string;
  if (!title) return { error: 'Title is required' };
  await savePost({ title });
  return { success: true };
}

function PostForm() {
  const [state, action, isPending] = useActionState(createPost, null);

  return (
    <form action={action}>
      <input name="title" />
      {state?.error && <p>{state.error}</p>}
      <button disabled={isPending}>
        {isPending ? 'Saving...' : 'Create Post'}
      </button>
    </form>
  );
}

4. Stop Using useEffect for These Things

This is the most common mistake. Here's what to replace:

❌ Deriving state with useEffect

// Wrong
const [fullName, setFullName] = useState('');
useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
// Correct β€” just compute it
const fullName = `${firstName} ${lastName}`;

❌ Fetching data with useEffect

// Wrong
useEffect(() => {
  fetch('/api/posts').then(r => r.json()).then(setPosts);
}, []);
// Correct β€” use a data fetching library
import { useQuery } from '@tanstack/react-query';

function Posts() {
  const { data, isLoading } = useQuery({
    queryKey: ['posts'],
    queryFn: () => fetch('/api/posts').then(r => r.json())
  });
}

Or even better β€” use Server Components in Next.js and fetch server-side:

// app/posts/page.tsx β€” This is a Server Component
export default async function PostsPage() {
  const posts = await getPosts(); // direct DB/API call
  return <PostList posts={posts} />;
}

❌ Responding to events with useEffect

// Wrong
useEffect(() => {
  if (submitted) {
    sendAnalytics();
    resetForm();
  }
}, [submitted]);
// Correct β€” handle it in the event handler
function handleSubmit() {
  sendAnalytics();
  resetForm();
  setSubmitted(true);
}

βœ… When useEffect IS appropriate

  • Setting up a third-party library that imperatively mutates the DOM
  • WebSocket or EventSource subscriptions (with cleanup)
  • Syncing to localStorage or sessionStorage
  • Firing analytics on route change (sparingly)

5. State Management in 2026

Need Tool
Local UI state useState / useReducer
Server data + caching TanStack Query
Global client state Zustand or Jotai
Forms React Hook Form + Zod
URL state nuqs (Next.js) or useSearchParams
Server state (Next.js) Server Actions + revalidatePath

Zustand setup (minimal):

import { create } from 'zustand';

interface CartStore {
  items: CartItem[];
  addItem: (item: CartItem) => void;
  removeItem: (id: string) => void;
}

export const useCartStore = create<CartStore>((set) => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] })),
  removeItem: (id) => set((state) => ({
    items: state.items.filter(i => i.id !== id)
  })),
}));

6. Performance in 2026 (The React Compiler Changes Everything)

React 19 ships with the React Compiler (previously "React Forget"). It automatically memoizes components and values β€” meaning useMemo, useCallback, and React.memo are rarely needed manually.

What the compiler handles automatically

  • Skipping re-renders when props haven't changed
  • Stabilizing callback references
  • Memoizing expensive computations

What you still need to think about

Code splitting β€” always split by route:

import { lazy, Suspense } from 'react';

const HeavyDashboard = lazy(() => import('./HeavyDashboard'));

function App() {
  return (
    <Suspense fallback={<LoadingScreen />}>
      <HeavyDashboard />
    </Suspense>
  );
}

Virtualize long lists β€” use TanStack Virtual:

import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }: { items: string[] }) {
  const parentRef = useRef<HTMLDivElement>(null);
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
  });

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
        {virtualizer.getVirtualItems().map(item => (
          <div
            key={item.key}
            style={{ position: 'absolute', top: item.start, width: '100%' }}
          >
            {items[item.index]}
          </div>
        ))}
      </div>
    </div>
  );
}

Images β€” always use Next.js <Image> or a CDN:

import Image from 'next/image';

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
  priority // for above-the-fold images
  placeholder="blur"
  blurDataURL={blurUrl}
/>

7. TypeScript Patterns Worth Knowing

Component props with variants

type ButtonProps = {
  variant: 'primary' | 'secondary' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  isLoading?: boolean;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;

function Button({ variant, size = 'md', isLoading, children, ...rest }: ButtonProps) {
  return (
    <button
      disabled={isLoading}
      className={cn(buttonVariants({ variant, size }))}
      {...rest}
    >
      {isLoading ? <Spinner /> : children}
    </button>
  );
}

Discriminated unions for component state

type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: string };

function DataView({ state }: { state: AsyncState<User[]> }) {
  if (state.status === 'loading') return <Skeleton />;
  if (state.status === 'error') return <Error message={state.error} />;
  if (state.status === 'success') return <UserList users={state.data} />;
  return null;
}

8. The 2026 Cheat Sheet

Hooks reference

Hook Use it for
useState Simple local state
useReducer Complex local state with multiple sub-values
useRef DOM refs, mutable values that don't trigger re-render
useContext Consuming context (theme, auth, locale)
use() Unwrapping promises and context in any component
useOptimistic Instant UI feedback while async operation runs
useActionState Managing form state tied to a server action
useTransition Marking non-urgent state updates
useDeferredValue Deferring re-rendering of slow sub-trees
useEffect LAST RESORT β€” external system sync only

When to reach for what

Scenario Solution
Data from server TanStack Query or Server Components
Form handling useActionState + Server Actions, or React Hook Form
Global state Zustand (simple) or Jotai (atomic)
URL as state nuqs
List of 500+ items TanStack Virtual
Async feedback useOptimistic
Heavy component lazy() + Suspense
Derived value Compute inline, no state
Animation Framer Motion or CSS transitions

Anti-patterns to avoid

Anti-pattern Replacement
useEffect for data fetching TanStack Query / Server Components
useEffect for derived state Compute inline
useEffect for event responses Event handler
Prop drilling 3+ levels Zustand / context
React.memo everywhere Let the compiler handle it
Manual useMemo/useCallback Let the compiler handle it
any in TypeScript Discriminated unions, generics
Giant components (500+ lines) Split by concern

9. Recommended Stack (2026)

Framework:     Next.js 15 (App Router)
Language:      TypeScript (strict mode)
Styling:       Tailwind CSS v4 + shadcn/ui
Data fetching: TanStack Query v5
Global state:  Zustand
Forms:         React Hook Form + Zod
Testing:       Vitest + Testing Library + Playwright
Linting:       ESLint + Prettier + TypeScript strict
Deployment:    Vercel / Cloudflare Workers

Final Thoughts

The shift from "React as a UI library" to "React as an application framework" is complete in 2026. The mental model has changed:

  • Server first. Render as much as you can on the server.
  • Data co-located with components. No more global fetch orchestration.
  • Less manual optimization. The compiler does the heavy lifting.
  • useEffect is a code smell. If you're reaching for it, ask why first.

Build from these foundations and you'll spend less time debugging re-renders and more time shipping features.

Found this useful? Drop a like and follow β€” I write about React, TypeScript, and modern web architecture.

Tags: #react #javascript #typescript #webdev #frontend

Comments (0)

Sign in to join the discussion

Be the first to comment!