Let's dive into new features in React.js 19.3

1.View Transitions

Basic usage

View Transitions is the <ViewTransition> component that Animates Adding and Removing Elements, transition, and sizing.

import { ViewTransition, useState, startTransition } from 'react'; export default function Component() { const [showItem, setShowItem] = useState(false); return ( <> <button onClick={() => { startTransition(() => { setShowItem((prev) => !prev); }); }}> {showItem ? '➖' : '➕'} </button>  {showItem && ( <ViewTransition> <Video video={videos[0]} />  </ViewTransition>  )} </>  ); } 

Example of defining the type of the transition

Different animations (e.g. enter, exit, update, share) are applied automatically.

Case that multiple animations are needed:

import { ViewTransition, addTransitionType, useState, startTransition } from 'react'; export default function Carousel() { const [selected, setSelected] = useState(0); return ( <> <button onClick={() => { startTransition(() => { addTransitionType('previous'); setSelected(c => c > 0 ? c - 1 : videos.length - 1); }); }}> ⬅️ </button>  <button onClick={() => { startTransition(() => { addTransitionType('next'); setSelected(c => c + 1 < videos.length ? c + 1 : 0); }); }}> ➡️ </button>  <ViewTransition enter={{ 'next': 'from-right', 'previous': 'from-left' }} exit={{ 'next': 'to-left', 'previous': 'to-right' }} > <Video video={videos[selected]} />  </ViewTransition>  </>  ); } 

Combination with Suspense

You can animate the transition from the fallback to the final content by wrapping boundary of Suspense with View Transition

import { Suspense, useState, startTransition, use, ViewTransition } from 'react'; export default function Component() { const [showItem, setShowItem] = useState(false); return ( <> <button onClick={() => { startTransition(() => { setShowItem((prev) => !prev); }); }}> {showItem ? '➖' : '➕'} </button>  {showItem && ( <ViewTransition update="auto" default="none"> <Suspense fallback={<VideoPlaceholder />}> <LazyVideo /> </Suspense>  </ViewTransition>  )} </>  ); } function LazyVideo() { const video = use(fetchVideo()); return <Video video={video} />; } 

2.Fragment Refs

Basic usage

Using Fragment Refs enables low-level control over DOM nodes within a group of sibling elements that do not share a single parent DOM element.

import { Fragment, useRef, useEffect } from 'react'; function Component() { const fragmentRef = useRef(null); useEffect(() => { const fragmentInstance = fragmentRef.current; fragmentInstance.focus(); // Move the focus }, []); return ( <Fragment ref={fragmentRef}> {posts.map(post => ( <Heading key={post.id}> {post.title} </Heading>  ))} </Fragment>  ); } 

An example of Usage

//InView Component import { Fragment, useRef, useEffect } from 'react'; function InView({ onChange, children }) { const fragmentRef = useRef(null); useEffect(() => { const fragmentInstance = fragmentRef.current; const observer = new IntersectionObserver(([entry]) => { onChange(entry.isIntersecting); }); fragmentInstance.observeUsing(observer); return () => { fragmentInstance.unobserveUsing(observer); }; }, [onChange]); return <Fragment ref={fragmentRef}>{children}</Fragment>; } export default function App() { const [isVisible, setIsVisible] = useState(true); return ( <div> <div>Scroll down</div>  <InView onChange={setIsVisible}> <Card title="First section" /> <Card title="Second section" /> </InView>  <div>Scroll up</div>  </div>  ); } 

3.Browser API

Basic usage

Calling use(browser()) enables to exclude the component that depends on browser API from SSR.

import { Suspense, use } from 'react'; import { browser } from 'react-dom'; function TimeZone() { use(browser()); // This component becomes Suspense on the server, while runs normally on the client. const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone; return <p>{timeZone}</p>; } export default function App() { return ( <> <p>Your current time zone is:</p>  <Suspense fallback="Loading..."> <TimeZone /> </Suspense>  </>  ); } 

Use under certain condition

function TimeZone({ defaultValue }) { if (defaultValue) { return <p>{defaultValue}</p>; // If defaultValue exists, returns `p` tag } use(browser()); // Otherwise, executes the browser API. const localTimeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone; return <p>{localTimeZone}</p>; } export default function App() { return ( <> <div> <p>The event's time zone is:</p> <TimeZone defaultValue="America/New_York" /> {/* Rendering on the server */} </div> <hr /> <div> <p>Your current time zone is:</p> <Suspense fallback="Loading..."> <TimeZone /> {/* Running on the client */} </Suspense> </div> </> ); } 

Use of useQuery

function useBrowserQuery(query, options) { if (options.initialData === undefined) { use(browser()); // If initialData doesn't exist, executes on browser. } return useQuery(query, options); } function ProductDetails({ productId, initialData }) { const product = useBrowserQuery(`/api/products/${productId}`, { initialData, }); return <h1>{product.name}</h1>; } 

4.React Server Components - Improving Context

Before improving(Need wrapper component)

// user-context.js 'use client'; import { createContext } from 'react'; export const UserContext = createContext(null); export function UserProvider({ currentUser, children }) { return <UserContext value={currentUser}>{children}</UserContext>; } // server-component.js import { UserProvider } from './user-context'; export async function Layout({ children }) { const currentUser = await getCurrentUser(); return ( <UserProvider currentUser={currentUser}> {children} </UserProvider>  ); } 

After improving(Render Context derectly)

You can render Context from 'use client' module on the server component directly without wrapper component(Provider).

// user-context.js 'use client'; import { createContext } from 'react'; export const UserContext = createContext(null); // server-component.js import { UserContext } from './user-context'; export async function Layout({ children }) { const currentUser = await getCurrentUser(); return ( <UserContext value={currentUser}> {children} </UserContext>  ); }