Skip to main content

React Native 0.74 - Yoga 3.0, Bridgeless New Architecture, and more

· 12 min read
Hur Ali
Software Engineer at Callstack
Alan Hughes
Software Engineer at Expo
Alfonso Curbelo
Software Engineer at Coinbase
Alex Hunt
Software Engineer at Meta
Nicola Corti
Software Engineer at Meta

Today we're releasing React Native 0.74! This release adds Yoga 3.0, Bridgeless by default under the New Architecture, batched onLayout updates (New Architecture), and Yarn 3 as the default package manager for new projects.

We are also removing deprecated APIs, with the removal of PropTypes and breaking changes to PushNotificationIOS. On Android, SDK 23 (Android 6.0) is now the minimum supported version.

Highlights

Breaking Changes

Highlights

Yoga 3.0

New Layout Behaviors

React Native 0.74 includes Yoga 3.0, the newest version of our layout engine. Yoga 3.0 improves layout by making styling more predictable, and supports rendering components written for the web.

React Native continues to intentionally preserve some incorrect layout behaviors, where fixing them was found to effect a significant number of real-world components. Layout conformance will be able to be configured more granularly in future versions of React Native.

warning

React Native previously flipped left/right (and start/end) edges when dealing with margin, padding, or border, set on a row-reverse container. Now, behavior of these properties lines up with web. Code which previously relied on edges being inverted may need to be updated to continue rendering correctly.

StyleBeforeAfter
<View
style={{
flexDirection: 'row',
backgroundColor: 'red',
margin: 10,
width: 200,
height: 100,
}}>
<View
style={{
flexDirection: 'row-reverse',
backgroundColor: 'blue',
flex: 1,
marginLeft: 50,
}}>
<View
style={{
backgroundColor: 'green',
height: '50%',
flex: 1,
marginLeft: 50,
}}
/>
</View>
</View>

Previous layout

New layout

Support for align-content: 'space-evenly'

Yoga 3.0 brings support for alignContent: 'space-evenly'. space-evenly distributes the lines in a multi-line flex container using evenly spaced gaps, placed between line and container edges.

Visual reference for alignContent behaviors
Source: World Wide Web Consortium

Support for position: 'static'

info

position: 'static' is supported only in the New Architecture.

Elements marked as position: 'static' may not be offset, and are not considered when determining the containing block of an absolutely positioned element. This allows positioning an element relative to an ancestor which is not its direct parent.

<View
style={{
backgroundColor: 'blue',
width: 200,
height: 200,
flexDirection: 'row-reverse',
}}>
<View
style={{
backgroundColor: 'red',
width: 100,
height: 100,
position: 'static',
}}>
<View
style={{
backgroundColor: 'green',
width: 25,
height: '25%',
left: 25,
top: 25,
position: 'absolute',
}}
/>
</View>
</View>

Static Example

Notice how the green <View> declares left and top and it is positioned relative to the blue <View>, not its parent.

React Native continues to default to position: 'relative' when no position is set.

New Architecture: Bridgeless by Default

In this release, we are making Bridgeless Mode the default when the New Architecture is enabled. You can learn more about our switch to Bridgeless as the default in this post. To make the transition smoother we enhanced the interop layers to cover Bridgeless and worked with several libraries to make sure they will work in Bridgeless from day one.

Bridgeless is not the only interop layer we worked on: we improved the New Renderer Interop layers too. The most exciting bit is that it is now enabled by default: you don't need to specify the components that have to go through it! You can read more about them here.

Finally, if you want to learn more about the New Architecture, you can find documentation in the react-native-new-architecture repo. When the New Architecture becomes the default, this information will be incorporated into reactnative.dev.

New Architecture: Batched onLayout updates

State updates in onLayout callbacks are now batched. Previously, each state update in the onLayout event would result in a new render commit.

function MyComponent(props) {
const [state1, setState1] = useState(false);
const [state2, setState2] = useState(false);

return (
<View>
<View
onLayout={() => {
setState1(true);
}}>
<View
onLayout={() => {
// When this event is executed, state1's new value is no longer observable here.
setState2(true);
}}>
</View>
</View>
);
}

In 0.74, setState1 and setState2 updates are batched together. This change is expected behavior in React and allows for less re-renders.

danger

This change may break code that has relied on un-batched state updates. You'll need to refactor this code to use updater functions or equivalent.

Yarn 3 for New Projects

Yarn 3 is now the default JavaScript package manager for new projects initialized with React Native Community CLI.

Yarn 3.x will be used with nodeLinker: node-modules, a mode providing compatibility with React Native libraries. This replaces Yarn Classic (1.x, deprecated) as the previous default. To upgrade Yarn version inside your existing app you can follow this guide.

$ yarn --help
━━━ Yarn Package Manager - 3.6.4 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

$ yarn <command>

The Community CLI also supports initializing projects with other package managers via the --pm flag (read more).

Breaking Changes

Android Minimum SDK Bump (Android 6.0)

React Native 0.74 has a minimum Android SDK version requirement of 23 (Android 6.0). Previously, this was Android 5.0 (API 21). See our context for this change here.

Bonus: Android app size reduction

The minimum SDK bump, together with several improvements at our native build, allowed us to greatly reduce the app size on user devices.

For example a newly created app with React Native 0.74 occupies ~13% less space on user device, resulting in ~4MB saved on device.

Side-by-side comparison of a new React Native app in the Android system storage view

Removal of Deprecated PropTypes

Before 0.74, React Native continued to ship with PropTypes, an API that has been deprecated since React 15.5 in 2017! We are now removing all built-in PropTypes from React Native, reducing app size (26.4kB in a minified bundle) and memory overhead.

The following PropTypes properties are removed: Image.propTypes, Text.propTypes, TextInput.propTypes, ColorPropType, EdgeInsetsPropType, PointPropType, ViewPropTypes (see commit).

If your app or library relies on PropTypes, we highly recommend migrating to a type system like TypeScript.

API Changes to PushNotificationIOS (Deprecated)

In React Native 0.74, we are making steps to remove the deprecated PushNotificationIOS library. The changes in this release are focused on removing references to older iOS APIs. PushNotificationIOS has been migrated onto Apple’s User Notifications framework and exposes new APIs for scheduling and handling notifications.

In the next release (0.75), we are planning to remove this library, relocating it out of React Native core and into the community package, @react-native-community/push-notification-ios. If you are still relying on PushNotificationIOS, you’ll need to migrate over before the next release.

API Changes

The didRegisterUserNotificationSettings: callback on RCTPushNotificationManager was a no-op and has been deleted.

The following callbacks on RCTPushNotificationManager have been deprecated and will be removed in 0.75:

+ (void)didReceiveLocalNotification:(UILocalNotification *)notification;
+ (void)didReceiveRemoteNotification:(NSDictionary *)notification;

In order to retrieve the notification which launched the app using getInitialNotification(), you’ll now need to explicitly set the initialNotification on RCTPushNotificationManager:

[RCTPushNotificationManager setInitialNotification:response.notification];

On the JS side, properties on Notification have changed. alertAction and repeatInterval are now deprecated and will be removed in 0.75:

type Notification = {
...
// NEW: Seconds from now to display the notification.
fireIntervalSeconds?: ?number,

// CHANGED: Used only for scheduling notifications. Will be null when
// retrieving notifications using `getScheduledLocalNotifications` or
// `getDeliveredNotifications`.
soundName?: ?string,

// DEPRECATED: This was used for iOS's legacy UILocalNotification.
alertAction?: ?string,

// DEPRECATED: Use `fireDate` or `fireIntervalSeconds` instead.
repeatInterval?: ?string,
};

Finally, the handler parameter on PushNotificationIOS.removeEventListener is unused and has been removed.

💡 How to Migrate

iOS

Your AppDelegate will need to implement UNUserNotificationCenterDelegate. This should be done on app startup in application:willFinishLaunchingWithOptions: or application:didFinishLaunchingWithOptions: (see Apple Docs for more details).

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;

return YES;
}

Implement userNotificationCenter:willPresentNotification:withCompletionHandler:, which is called when a notification arrives and the app is in the foreground. Use the completionHandler to determine if the notification will be shown to the user and notify RCTPushNotificationManager accordingly:

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
{
// This will trigger 'notification' and 'localNotification' events on PushNotificationIOS
[RCTPushNotificationManager didReceiveNotification:notification];
// Decide if and how the notification will be shown to the user
completionHandler(UNNotificationPresentationOptionNone);
}

To handle when a notification is tapped, implement userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:. Note that if you set foreground notifications to be shown in userNotificationCenter:willPresentNotification:withCompletionHandler:, you should only notify RCTPushNotificationManager in one of these callbacks.

If the tapped notification resulted in app launch, call setInitialNotification:. If the notification was not previously handled by userNotificationCenter:willPresentNotification:withCompletionHandler:, call didReceiveNotification: as well:

- (void)  userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler
{
// This condition passes if the notification was tapped to launch the app
if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) {
// Allow the notification to be retrieved on the JS side using getInitialNotification()
[RCTPushNotificationManager setInitialNotification:response.notification];
}
// This will trigger 'notification' and 'localNotification' events on PushNotificationIOS
[RCTPushNotificationManager didReceiveNotification:response.notification];
completionHandler();
}

Finally, delete the following methods and adapt the logic into the callbacks above which will be called instead:

  1. application:didReceiveLocalNotification: [deprecated]
  2. application:didReceiveRemoteNotification: [deprecated]
  3. application:didReceiveRemoteNotification:fetchCompletionHandler: [not deprecated, but is superseded by the UNUserNotificationCenterDelegate methods]

Delete any usages of application:didRegisterUserNotificationSettings: and RCTPushNotificationManager’s corresponding didRegisterUserNotificationSettings: as well.

Example: See the RNTester AppDelegate.mm.

JS

  1. Remove any references to alertAction.
  2. Remove the handler argument on any calls to removeEventListener.
  3. Replace any usages of repeatInterval by firing multiple notifications using fireDate or fireIntervalSeconds instead.
  4. Note that soundName will be null when it is accessed on a Notification returned from getScheduledLocalNotifications() and getDeliveredNotifications().

Removal of Flipper React Native Plugin

Use of Flipper for inspecting React Native layouts, network requests, and other React Native plugin features, is now unsupported. In 0.74, we have removed the native Flipper libraries and setup code from new React Native projects. This means fewer dependencies and quicker local setup (see original RFC).

The diff for removing Flipper in your app can be seen in the Upgrade Helper. If you want to preserve Flipper in an existing app, ignore the relevant diff lines.

💡 To re-integrate Flipper

Flipper can still be used as a standalone tool for debugging an Android or iOS app, and can be manually integrated by following the Flipper docs (Android guide, iOS guide).

We recommend that teams invest in switching to native debugging tooling in Android Studio and Xcode.

tip

Replacing Flipper

There are a number of dedicated debugging tools which replace Flipper features. For more information, we recommend reading the excellent Why you don't need Flipper in your React Native app article by Jamon Holmgren.

JavaScript debugging

Using the Hermes Debugger remains our recommended debugging option for 0.74. You can also try the Experimental New Debugger, which is also the default in Expo. This continues to be an early preview — known issues and updates can be followed here.

Other Breaking Changes

General

  • Make start/end in styles always refer to writing direction (#42251).

Android

  • Remove of JSIModule* from FabricUIManagerProvider (#42059).
    • This API was unused in open source — use TurboModules instead.
  • Deprecate UIManagerModule.showPopupMenu and UIManagerModule.dismissPopupMenu (#42441)

iOS

  • Delete configFilename and configKey arguments from iOS codegen CLI (#41533).
  • Change how bundleURL is handled (#43994).
    • Before, bundleURL was set when React Native was started in an instance variable and it was not possible to update it.
    • Now, bundleUrl is a function which is re-evaluated when needed, enabling the use of a different URL across refreshes.
    • This change will affect your app only if you were changing the bundleURL variable after the app is started. In this case, move the logic that updates the variable to the bundleURL function in AppDelegate.

Please see the full changelog for a complete list of breaking changes.

Known Issues

iOS

  • Edge case when using multiple windows: When the main window is inactive and the system tries to present a dialog, the dialog is not presented in the right position on the screen. A fix is incoming in #44167 and will ship in 0.74.1.

Acknowledgements

React Native 0.74 contains over 1673 commits from 57 contributors. Thanks for all your hard work!

Thanks to all the additional authors that worked on documenting features in this release post:

Upgrade to 0.74

Please use the React Native Upgrade Helper to view code changes between React Native versions for existing projects, in addition to the Upgrading docs.

To create a new project:

npx react-native@latest init MyProject

If you use Expo, React Native 0.74 will be supported in Expo SDK 51.

info

0.74 is now the latest stable version of React Native and 0.71.x moves to unsupported. For more information see React Native's support policy. We aim to publish a final end-of-life update of 0.71 at the beginning of May.