Skip to main content
React

React 19 Complete Guide: Revolutionary Features and Best Practices

By Tech Blog55 min read
React 19 Complete Guide: Revolutionary Features and Best Practices

React 19 Complete Guide: Revolutionary Features and Best Practices

React 19 marks a transformative milestone in web development, introducing revolutionary features that fundamentally change how we build applications. This comprehensive guide explores Server Actions, new Hooks, performance optimizations, and best practices for modern React development.

What's New in React 19?

React 19 introduces several game-changing features:

  • Server Actions: Call server functions directly from client components
  • New Hooks: use(), useOptimistic(), useFormStatus(), useFormState()
  • Transitions API: Non-blocking UI updates for better UX
  • React Compiler: Automatic optimization without manual memoization
  • Enhanced Suspense: Better streaming and error handling
  • Improved TypeScript: Better type inference and safety

1. Server Actions: The Game Changer

Server Actions eliminate the need for separate API routes, allowing direct server function calls from client components.

1.1 Basic Server Action

// app/actions.ts
'use server'

import { db } from '@/lib/database'
import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string

  await db.posts.create({
    data: { title, content, published: true }
  })

  revalidatePath('/blog')
  return { success: true }
}

Using in components:

// app/components/CreatePostForm.tsx
'use client'

import { createPost } from '@/app/actions'

export default function CreatePostForm() {
  return (
    <form action={createPost}>
      <input name="title" placeholder="Post title" required />
      <textarea name="content" placeholder="Content" required />
      <button type="submit">Create Post</button>
    </form>
  )
}

1.2 Server Actions with Validation

'use server'

import { z } from 'zod'

const PostSchema = z.object({
  title: z.string().min(1).max(100),
  content: z.string().min(10),
  tags: z.array(z.string()).optional()
})

export async function createPost(formData: FormData) {
  try {
    const validatedData = PostSchema.parse({
      title: formData.get('title'),
      content: formData.get('content'),
      tags: formData.get('tags')?.toString().split(',')
    })

    const post = await db.posts.create({ data: validatedData })

    revalidatePath('/blog')
    return { success: true, postId: post.id }
  } catch (error) {
    if (error instanceof z.ZodError) {
      return { success: false, errors: error.flatten().fieldErrors }
    }
    return { success: false, message: 'Failed to create post' }
  }
}

2. The use() Hook: Simplified Async

The use() Hook reads Promise or Context values directly in components.

2.1 use() with Promises

import { use, Suspense } from 'react'

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

function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise)

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}

export default function Page({ params }: { params: { id: string } }) {
  const userPromise = fetchUser(params.id)

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <UserProfile userPromise={userPromise} />
    </Suspense>
  )
}

2.2 Conditional use() Calls

Unlike traditional Hooks, use() can be called conditionally:

function UserData({ userId }: { userId?: string }) {
  let user = null

  if (userId) {
    const userPromise = fetchUser(userId)
    user = use(userPromise) // Conditional call is allowed!
  }

  return user ? <div>{user.name}</div> : <div>No user</div>
}

3. useOptimistic(): Instant UI Updates

useOptimistic() enables optimistic updates for instant user feedback.

3.1 Basic Optimistic Updates

'use client'

import { useOptimistic } from 'react'
import { likePost } from '@/app/actions'

export default function PostCard({ post }: { post: Post }) {
  const [optimisticPost, addOptimisticLike] = useOptimistic(
    post,
    (current, amount: number) => ({
      ...current,
      likes: current.likes + amount
    })
  )

  async function handleLike() {
    addOptimisticLike(1) // Update UI immediately
    await likePost(post.id) // Send to server
  }

  return (
    <div>
      <h2>{optimisticPost.title}</h2>
      <button onClick={handleLike}>❤️ {optimisticPost.likes}</button>
    </div>
  )
}

3.2 Optimistic Form Submissions

'use client'

import { useOptimistic, useTransition } from 'react'

export default function CommentList({ comments }: { comments: Comment[] }) {
  const [optimisticComments, addOptimistic] = useOptimistic(
    comments,
    (state, newComment: Comment) => [...state, { ...newComment, pending: true }]
  )

  const [isPending, startTransition] = useTransition()

  async function handleSubmit(formData: FormData) {
    const text = formData.get('comment') as string

    startTransition(async () => {
      addOptimistic({ id: crypto.randomUUID(), text, author: 'You' })
      await addComment(text)
    })
  }

  return (
    <div>
      {optimisticComments.map(comment => (
        <div key={comment.id} className={comment.pending ? 'opacity-50' : ''}>
          <strong>{comment.author}:</strong> {comment.text}
        </div>
      ))}
      <form action={handleSubmit}>
        <input name="comment" placeholder="Add comment..." />
        <button type="submit" disabled={isPending}>Post</button>
      </form>
    </div>
  )
}

4. useFormStatus() and useFormState()

New Hooks for better form handling.

4.1 useFormStatus()

'use client'

import { useFormStatus } from 'react'

export function SubmitButton() {
  const { pending } = useFormStatus()

  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Submitting...' : 'Submit'}
    </button>
  )
}

4.2 useFormState()

'use client'

import { useFormState } from 'react'
import { createPost } from '@/app/actions'

export default function CreatePostForm() {
  const [state, formAction] = useFormState(createPost, { message: '' })

  return (
    <form action={formAction}>
      <input name="title" />
      {state.errors?.title && <p className="text-red-500">{state.errors.title}</p>}

      <textarea name="content" />
      {state.errors?.content && <p className="text-red-500">{state.errors.content}</p>}

      <button type="submit">Create</button>
      {state.message && <p>{state.message}</p>}
    </form>
  )
}

5. Transitions API

Mark updates as non-urgent to keep UI responsive.

5.1 useTransition()

'use client'

import { useState, useTransition } from 'react'

export default function SearchResults() {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState([])
  const [isPending, startTransition] = useTransition()

  function handleSearch(value: string) {
    setQuery(value) // Urgent update

    startTransition(async () => {
      const data = await fetch(`/api/search?q=${value}`).then(r => r.json())
      setResults(data) // Non-urgent update
    })
  }

  return (
    <div>
      <input value={query} onChange={(e) => handleSearch(e.target.value)} />
      {isPending && <div>Searching...</div>}
      <ul>
        {results.map(r => <li key={r.id}>{r.title}</li>)}
      </ul>
    </div>
  )
}

6. React Compiler

Automatic optimization without manual memoization.

Before: Manual Optimization

import { memo, useMemo, useCallback } from 'react'

const Component = memo(function Component({ data }) {
  const processed = useMemo(() => expensiveOp(data), [data])
  const handleClick = useCallback(() => console.log('click'), [])
  return <div onClick={handleClick}>{processed}</div>
})

After: Automatic

// No memo, useMemo, or useCallback needed!
function Component({ data }) {
  const processed = expensiveOp(data)
  const handleClick = () => console.log('click')
  return <div onClick={handleClick}>{processed}</div>
}

7. Integration with Next.js 15

React 19 works seamlessly with Next.js 15.

Server Components + Server Actions

// app/posts/page.tsx
import { getPosts, deletePost } from '@/lib/actions'

export default async function PostsPage() {
  const posts = await getPosts()

  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <form action={deletePost}>
            <input type="hidden" name="postId" value={post.id} />
            <button type="submit">Delete</button>
          </form>
        </article>
      ))}
    </div>
  )
}

8. TypeScript Support

Excellent TypeScript integration.

'use server'

interface CreatePostInput {
  title: string
  content: string
}

interface CreatePostResult {
  success: boolean
  postId?: string
  errors?: Record<string, string[]>
}

export async function createPost(input: CreatePostInput): Promise<CreatePostResult> {
  // Type-safe implementation
}

9. Performance Best Practices

Optimize Server Actions

'use server'

import { cache } from 'react'

export const getUser = cache(async (id: string) => {
  return await db.users.findUnique({ where: { id } })
})

export async function getUserWithPosts(userId: string) {
  const [user, posts] = await Promise.all([
    db.users.findUnique({ where: { id: userId } }),
    db.posts.findMany({ where: { authorId: userId } })
  ])
  return { user, posts }
}

10. Migration Guide

Upgrade from React 18

npm install react@19 react-dom@19 next@15

Common Issues

// ❌ Old: ReactDOM.render (removed)
ReactDOM.render(<App />, root)

// ✅ New: createRoot
import { createRoot } from 'react-dom/client'
createRoot(root).render(<App />)

Related Resources

Conclusion

React 19 revolutionizes web development with Server Actions, new Hooks, and automatic optimizations. Start building faster, more responsive applications today!

Key takeaways:

  • Server Actions simplify data mutations
  • use() Hook simplifies async handling
  • useOptimistic() enables instant feedback
  • React Compiler eliminates manual optimization
  • Seamless Next.js 15 integration

Comments

Share your thoughts and join the discussion

Login required to comment0 / 1000

Please or to comment

Comments (0)

Related Articles