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> ); }