Skip to content

Commit 107f1be

Browse files
committed
wip
1 parent f7b98b7 commit 107f1be

File tree

2 files changed

+27
-37
lines changed

2 files changed

+27
-37
lines changed

src/content/blog/2025/04/01/react-labs-what-we-have-been-working-on-april-2025.md

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ April 1, 2025 by [Ricky Hanlon](https://twitter.com/rickhanlonii), and [Matt Car
1111

1212
<Intro>
1313

14-
In React Labs posts we write about projects in active research and development. In this post, we're sharing two new experimental features that are ready to try today, and updates on other areas we're working on now.
14+
In React Labs posts, we write about projects in active research and development. In this post, we're sharing two new experimental features that are ready to try today, and updates on other areas we're working on now.
1515

1616
</Intro>
1717

@@ -20,13 +20,13 @@ In React Labs posts we write about projects in active research and development.
2020

2121
React Conf 2025 is scheduled for October 7–8 in Henderson, Nevada!
2222

23-
We're looking for speakers to work with us to create talks covering the features we're working on in this post. If you're interested in speaking at ReactConf, [please apply here](https://forms.reform.app/react-conf/call-for-speakers/piaae1?ga4_visitor_id=c3e8f3ce-2004-47a5-b801-f6b308280acd) (no talk proposal required).
23+
We're looking for speakers to help us create talks about the features covered in this post. If you're interested in speaking at ReactConf, [please apply here](https://forms.reform.app/react-conf/call-for-speakers/piaae1?ga4_visitor_id=c3e8f3ce-2004-47a5-b801-f6b308280acd) (no talk proposal required).
2424

2525
For more info on tickets, free streaming, sponsoring, and more, see [the React Conf website](https://conf.react.dev).
2626

2727
</Note>
2828

29-
Today, we're excited to release docs for two new experimental features ready for testing:
29+
Today, we're excited to release documentation for two new experimental features that are ready for testing:
3030

3131
- [View Transitions](#view-transitions)
3232
- [Activity](#activity)
@@ -42,24 +42,24 @@ We're also sharing updates on new features currently in development:
4242

4343
# New Experimental Features {/*new-experimental-features*/}
4444

45-
Today we're sharing two new experimental features: View Transitions and Activity.
45+
View Transitions and Activity are now ready for testing in `react@experimental`.These features have been tested in production and are stable, but the final API may still change as we incorporate feedback.
46+
4647

47-
These experimental features have been tested in production and we are confident they're stable, but the final API might change while we iterate on feedback.
4848

4949
You can try them by upgrading React packages to the most recent experimental version:
5050

5151
- `react@experimental`
5252
- `react-dom@experimental`
5353

54-
Read more to see how to use these features in your app, or check out the new docs published today:
54+
Read on to learn how to use these features in your app, or check out the newly published docs:
5555

5656
- [`<ViewTransition>`](/reference/react/ViewTransition): A component lets you activate an animation for a Transition.
5757
- [`addTransitionType`](/reference/react/addTransitionType): A function that allows you to specify the cause of a Transition.
5858
- [`<Activity>`](/reference/react/Activity): A component that lets you hide and show part of the UI.
5959

6060
## View Transitions {/*view-transitions*/}
6161

62-
React View Transitions are a new experimental feature for React designed to make it easy to add animations to the UI transitons in your app. Under-the-hood, these animations APIs use the new [`startViewTransition`](https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition) API available in most modern browsers.
62+
React View Transitions are a new experimental feature that makes it easier to add animations to UI transitions in your app. Under-the-hood, these animations APIs use the new [`startViewTransition`](https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition) API available in most modern browsers.
6363

6464
To opt-in to animating an element, wrap it in the new `<ViewTransition>` component:
6565

@@ -90,7 +90,7 @@ const deferred = useDeferredValue(value);
9090
</Suspense>
9191
```
9292

93-
By default, these animations have the [default CSS animations for View Transitions](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API/Using#customizing_your_animations) applied (most are given a default smooth cross-fade). You can use [view transition pseudo-selectors](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API/Using#the_view_transition_pseudo-element_tree) to define "how" the animation runs. For example, using `*` we can change the default for all animations:
93+
By default, these animations use the [default CSS animations for View Transitions](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API/Using#customizing_your_animations) applied (typically a smooth cross-fade). You can use [view transition pseudo-selectors](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API/Using#the_view_transition_pseudo-element_tree) to define "how" the animation runs. For example, you can use `*` to change the default animation for all transitions:
9494

9595
```
9696
// "how" to animate.
@@ -102,13 +102,13 @@ By default, these animations have the [default CSS animations for View Transitio
102102
}
103103
```
104104

105-
When the DOM updates in an animation trigger such as `startTransition`, `useDeferredValue`, or switching Suspense fallbacks to content, React will use [declarative heuristics](/reference/react/ViewTransition#viewtransition) to automatically determine which `<ViewTransition>` components to activate for the animation. The browser will then run the animation that's defined in CSS.
105+
When the DOM updates due to an animation trigger&mdash;like `startTransition`, `useDeferredValue`, or a `Suspense` fallback switching to content&mdash;React will use [declarative heuristics](/reference/react/ViewTransition#viewtransition) to automatically determine which `<ViewTransition>` components to activate for the animation. The browser will then run the animation that's defined in CSS.
106106

107-
If you're familiar with the browser view transition APIs and curious how we've built support for them into React, check out [How does `<ViewTransition>` Work](/reference/react/ViewTransition#how-does-viewtransition-work) in the docs.
107+
If you're familiar with the browser's View Transition API and want to know how React supports it, check out [How does `<ViewTransition>` Work](/reference/react/ViewTransition#how-does-viewtransition-work) in the docs.
108108

109109
In this post, let's take a look at a few examples of how to use View Transitions.
110110

111-
We'll start this app, which doesn't animate any of these interactions:
111+
We'll start with this app, which doesn't animate any of the following interactions:
112112
- Click a video to view the details.
113113
- Click "back" to go back to the feed.
114114
- Type in the list to filter the videos.
@@ -120,8 +120,8 @@ import TalkDetails from './Details'; import Home from './Home'; import {useRoute
120120

121121
export default function App() {
122122
const {url} = useRouter();
123-
124-
// 🚩Starting without animations
123+
124+
// 🚩This version doesn't include any animations yet
125125
return url === '/' ? <Home /> : <TalkDetails />;
126126
}
127127
```
@@ -153,7 +153,7 @@ function VideoInfoFallback() {
153153
);
154154
}
155155

156-
export default function Details({}) {
156+
export default function Details() {
157157
const { url, navigateBack } = useRouter();
158158
const videoId = url.split("/").pop();
159159
const video = use(fetchVideo(videoId));
@@ -206,7 +206,6 @@ function SearchInput({ value, onChange }) {
206206
<input
207207
type="text"
208208
id={id}
209-
className="flex ps-11 py-4 h-10 w-full text-start bg-secondary-button outline-none betterhover:hover:bg-opacity-80 pointer items-center text-primary rounded-full align-middle text-base"
210209
placeholder="Search"
211210
value={value}
212211
onChange={(e) => onChange(e.target.value)}
@@ -1347,7 +1346,7 @@ function VideoInfoFallback() {
13471346
);
13481347
}
13491348

1350-
export default function Details({}) {
1349+
export default function Details() {
13511350
const { url, navigateBack } = useRouter();
13521351
const videoId = url.split("/").pop();
13531352
const video = use(fetchVideo(videoId));
@@ -1400,7 +1399,6 @@ function SearchInput({ value, onChange }) {
14001399
<input
14011400
type="text"
14021401
id={id}
1403-
className="flex ps-11 py-4 h-10 w-full text-start bg-secondary-button outline-none betterhover:hover:bg-opacity-80 pointer items-center text-primary rounded-full align-middle text-base"
14041402
placeholder="Search"
14051403
value={value}
14061404
onChange={(e) => onChange(e.target.value)}
@@ -2539,7 +2537,7 @@ function VideoInfoFallback() {
25392537
);
25402538
}
25412539

2542-
export default function Details({}) {
2540+
export default function Details() {
25432541
const { url, navigateBack } = useRouter();
25442542
const videoId = url.split("/").pop();
25452543
const video = use(fetchVideo(videoId));
@@ -2592,7 +2590,6 @@ function SearchInput({ value, onChange }) {
25922590
<input
25932591
type="text"
25942592
id={id}
2595-
className="flex ps-11 py-4 h-10 w-full text-start bg-secondary-button outline-none betterhover:hover:bg-opacity-80 pointer items-center text-primary rounded-full align-middle text-base"
25962593
placeholder="Search"
25972594
value={value}
25982595
onChange={(e) => onChange(e.target.value)}
@@ -3730,7 +3727,7 @@ function VideoInfoFallback() {
37303727
);
37313728
}
37323729

3733-
export default function Details({}) {
3730+
export default function Details() {
37343731
const { url, navigateBack } = useRouter();
37353732
const videoId = url.split("/").pop();
37363733
const video = use(fetchVideo(videoId));
@@ -3783,7 +3780,6 @@ function SearchInput({ value, onChange }) {
37833780
<input
37843781
type="text"
37853782
id={id}
3786-
className="flex ps-11 py-4 h-10 w-full text-start bg-secondary-button outline-none betterhover:hover:bg-opacity-80 pointer items-center text-primary rounded-full align-middle text-base"
37873783
placeholder="Search"
37883784
value={value}
37893785
onChange={(e) => onChange(e.target.value)}
@@ -4982,7 +4978,7 @@ function VideoInfoFallback() {
49824978
);
49834979
}
49844980

4985-
export default function Details({}) {
4981+
export default function Details() {
49864982
const { url, navigateBack } = useRouter();
49874983
const videoId = url.split("/").pop();
49884984
const video = use(fetchVideo(videoId));
@@ -5035,7 +5031,6 @@ function SearchInput({ value, onChange }) {
50355031
<input
50365032
type="text"
50375033
id={id}
5038-
className="flex ps-11 py-4 h-10 w-full text-start bg-secondary-button outline-none betterhover:hover:bg-opacity-80 pointer items-center text-primary rounded-full align-middle text-base"
50395034
placeholder="Search"
50405035
value={value}
50415036
onChange={(e) => onChange(e.target.value)}
@@ -6271,7 +6266,7 @@ function VideoInfoFallback() {
62716266
);
62726267
}
62736268

6274-
export default function Details({}) {
6269+
export default function Details() {
62756270
const { url, navigateBack } = useRouter();
62766271
const videoId = url.split("/").pop();
62776272
const video = use(fetchVideo(videoId));
@@ -6331,7 +6326,6 @@ function SearchInput({ value, onChange }) {
63316326
<input
63326327
type="text"
63336328
id={id}
6334-
className="flex ps-11 py-4 h-10 w-full text-start bg-secondary-button outline-none betterhover:hover:bg-opacity-80 pointer items-center text-primary rounded-full align-middle text-base"
63356329
placeholder="Search"
63366330
value={value}
63376331
onChange={(e) => onChange(e.target.value)}
@@ -7589,7 +7583,7 @@ function VideoInfoFallback() {
75897583
);
75907584
}
75917585

7592-
export default function Details({}) {
7586+
export default function Details() {
75937587
const { url, navigateBack } = useRouter();
75947588
const videoId = url.split("/").pop();
75957589
const video = use(fetchVideo(videoId));
@@ -7679,7 +7673,6 @@ function SearchInput({ value, onChange }) {
76797673
<input
76807674
type="text"
76817675
id={id}
7682-
className="flex ps-11 py-4 h-10 w-full text-start bg-secondary-button outline-none betterhover:hover:bg-opacity-80 pointer items-center text-primary rounded-full align-middle text-base"
76837676
placeholder="Search"
76847677
value={value}
76857678
onChange={(e) => onChange(e.target.value)}
@@ -8903,7 +8896,7 @@ function VideoInfoFallback() {
89038896
);
89048897
}
89058898

8906-
export default function Details({}) {
8899+
export default function Details() {
89078900
const { url, navigateBack } = useRouter();
89088901
const videoId = url.split("/").pop();
89098902
const video = use(fetchVideo(videoId));
@@ -8993,7 +8986,6 @@ function SearchInput({ value, onChange }) {
89938986
<input
89948987
type="text"
89958988
id={id}
8996-
className="flex ps-11 py-4 h-10 w-full text-start bg-secondary-button outline-none betterhover:hover:bg-opacity-80 pointer items-center text-primary rounded-full align-middle text-base"
89978989
placeholder="Search"
89988990
value={value}
89998991
onChange={(e) => onChange(e.target.value)}
@@ -10264,7 +10256,7 @@ function VideoInfoFallback() {
1026410256
);
1026510257
}
1026610258

10267-
export default function Details({}) {
10259+
export default function Details() {
1026810260
const { url, navigateBack } = useRouter();
1026910261
const videoId = url.split("/").pop();
1027010262
const video = use(fetchVideo(videoId));
@@ -10354,7 +10346,6 @@ function SearchInput({ value, onChange }) {
1035410346
<input
1035510347
type="text"
1035610348
id={id}
10357-
className="flex ps-11 py-4 h-10 w-full text-start bg-secondary-button outline-none betterhover:hover:bg-opacity-80 pointer items-center text-primary rounded-full align-middle text-base"
1035810349
placeholder="Search"
1035910350
value={value}
1036010351
onChange={(e) => onChange(e.target.value)}
@@ -11694,7 +11685,6 @@ function SearchInput({ value, onChange }) {
1169411685
<input
1169511686
type="text"
1169611687
id={id}
11697-
className="flex ps-11 py-4 h-10 w-full text-start bg-secondary-button outline-none betterhover:hover:bg-opacity-80 pointer items-center text-primary rounded-full align-middle text-base"
1169811688
placeholder="Search"
1169911689
value={value}
1170011690
onChange={(e) => onChange(e.target.value)}
@@ -12975,10 +12965,10 @@ We're also researching ways to enhance View Transitions to support gesture anima
1297512965
Gestures present new challenges for a few reasons:
1297612966

1297712967
- **Gestures are continuous**: as you swipe the animation is tied to your finger placement time, rather than triggering and running to completion.
12978-
- **Gestures don't complete:**: when you release your finger gesture animtaions can run to completion, or revert to their original state (like when you only partially open a menu) depending on how far you go.
12968+
- **Gestures don't complete**: when you release your finger gesture animations can run to completion, or revert to their original state (like when you only partially open a menu) depending on how far you go.
1297912969
- **Gestures invert old and new**: while you're animating, you want the page you are animating from to stay "alive" and interactive. This inverts the browser View Transition model where the "old" state is a snapshot and the "new" state is the live DOM.
1298012970

12981-
We have an approach we believe will work well, which may introduce a new API to trigger gesture transitions, but we're currently focused on shipping `<ViewTransition>` and re-visit gestures after it ships.
12971+
We believe we’ve found an approach that works well and may introduce a new API for triggering gesture transitions. For now, we're focused on shipping `<ViewTransition>`, and will revisit gestures afterward.
1298212972

1298312973
---
1298412974

@@ -12988,13 +12978,13 @@ When we released React 18 with concurrent rendering, we also released `useSyncEx
1298812978

1298912979
Using `useSyncExternalStore` comes at a cost though, since it forces bail out from concurrent features like transitions, and forces existing content to show Suspense fallbacks.
1299012980

12991-
Now that React 19 has shipped, we're re-visiting this problem space to create a primiative to fully support concurrent external stores with the `use` API:
12981+
Now that React 19 has shipped, we're re-visiting this problem space to create a primitive to fully support concurrent external stores with the `use` API:
1299212982

1299312983
```js
1299412984
const value = use(store);
1299512985
```
1299612986

12997-
Our goal is to allow state stored outside of React to be read in render without tearing, and to work seamlessly with all of the concurrent features React offers.
12987+
Our goal is to allow external state to be read during render without tearing, and to work seamlessly with all of the concurrent features React offers.
1299812988

1299912989
This research is still early. We'll share more, and what the new APIs will look like, when we're further along.
1300012990

src/content/blog/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ You can also follow the [@react.dev](https://bsky.app/profile/react.dev) account
1414

1515
<BlogCard title="React Labs: What We've Been Working On – April 2025" date="April 1, 2025" url="/blog/2025/04/01/react-labs-what-we-have-been-working-on-april-2025">
1616

17-
In React Labs posts we write about projects in active research and development. In this post, we're sharing two new experimental features that are ready to try today, and sharing other areas we're working on now.
17+
In React Labs posts, we write about projects in active research and development. In this post, we're sharing two new experimental features that are ready to try today, and sharing other areas we're working on now.
1818

1919
</BlogCard>
2020

0 commit comments

Comments
 (0)