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>
);
}
Top comments (0)