Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion packages/react-native/Libraries/Alert/RCTAlertManager.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,25 @@ import NativeDialogManagerAndroid from '../NativeModules/specs/NativeDialogManag

function emptyCallback() {}

/**
* Shows an alert dialog on Android using the native DialogManagerAndroid module.
* Falls back gracefully if the native module is not available.
*
* @param {Args} args - The alert configuration (title, message, buttons, etc.)
* @param {Function} callback - Callback function invoked with (id, value) when user selects an option
* @returns {void}
* @note NativeDialogManagerAndroid provides better Android-specific UI/UX than the
* standard AlertManager. If unavailable, the alert silently fails to prevent crashes.
* See TODO(5998984) for improved polyfill implementation.
*/
export function alertWithArgs(
args: Args,
callback: (id: number, value: string) => void,
) {
// TODO(5998984): Polyfill it correctly with DialogManagerAndroid
if (!NativeDialogManagerAndroid) {
console.warn(
'NativeDialogManagerAndroid is not available. Alert may not be displayed.',
);
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,15 @@ type OnAnimationDidFailCallback = () => void;
let isLayoutAnimationEnabled: boolean =
ReactNativeFeatureFlags.isLayoutAnimationEnabled();

/**
* Internal function to enable or disable layout animations.
* When disabled, configureNext calls will be no-ops.
*
* @param {boolean} value - Whether to enable layout animations
* @internal
*/
function setLayoutAnimationEnabled(value: boolean) {
isLayoutAnimationEnabled = isLayoutAnimationEnabled;
isLayoutAnimationEnabled = value;
}

/**
Expand Down
18 changes: 12 additions & 6 deletions packages/react-native/Libraries/Utilities/FeatureDetection.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,25 @@
*/

/**
* @return whether or not a @param {function} f is provided natively by calling
* `toString` and check if the result includes `[native code]` in it.
* Detects if a function is a native function (not a polyfill or user-defined).
*
* Note that a polyfill can technically fake this behavior but few does it.
* Therefore, this is usually good enough for our purpose.
* Checks if the function's toString representation includes `[native code]`.
* Note: A polyfill can technically fake this, but most don't, making this
* check reliable for practical purposes.
*
* @param {Function} f - Function to check
* @returns {boolean} True if the function is a native function
*/
export function isNativeFunction(f: Function): boolean {
return typeof f === 'function' && f.toString().indexOf('[native code]') > -1;
}

/**
* @return whether or not the constructor of @param {object} o is an native
* function named with @param {string} expectedName.
* Checks if an object's constructor is a native function with a specific name.
*
* @param {Object} o - Object to check
* @param {string} expectedName - Expected constructor name
* @returns {boolean} True if the constructor is native and matches the expected name
*/
export function hasNativeConstructor(o: Object, expectedName: string): boolean {
const con = Object.getPrototypeOf(o).constructor;
Expand Down
22 changes: 20 additions & 2 deletions packages/react-native/Libraries/Utilities/RCTLog.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ const levelsMap = {
let warningHandler: ?(...Array<unknown>) => void = null;

const RCTLog = {
// level one of log, info, warn, error, mustfix
/**
* Logs to console only if the native logging hook is unavailable.
* If available, delegates to native logging and reports warnings to LogBox.
*
* @param {string} level - Log level: 'log', 'info', 'warn', 'error', or 'fatal'
* @param {...any} args - Arguments to log
*/
logIfNoNativeHook(level: string, ...args: Array<unknown>): void {
// We already printed in the native console, so only log here if using a js debugger
if (typeof global.nativeLoggingHook === 'undefined') {
Expand All @@ -36,7 +42,14 @@ const RCTLog = {
}
},

// Log to console regardless of nativeLoggingHook
/**
* Logs to console regardless of native logging hook availability.
* Throws an invariant if the log level is invalid.
*
* @param {string} level - Log level: 'log', 'info', 'warn', 'error', or 'fatal'
* @param {...any} args - Arguments to log
* @throws Invariant error if level is not a valid log level
*/
logToConsole(level: string, ...args: Array<unknown>): void {
// $FlowFixMe[invalid-computed-prop]
const logFn = levelsMap[level];
Expand All @@ -48,6 +61,11 @@ const RCTLog = {
console[logFn](...args);
},

/**
* Sets a custom warning handler to process warnings reported to LogBox.
*
* @param {?Function} handler - Function to handle warnings, or null to remove the handler
*/
setWarningHandler(handler: typeof warningHandler): void {
warningHandler = handler;
},
Expand Down
17 changes: 17 additions & 0 deletions packages/react-native/Libraries/Utilities/SceneTracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,33 @@ let _listeners: Array<(scene: Scene) => void> = [];

let _activeScene: Scene = {name: 'default'};

/**
* Global scene tracker for managing the currently active scene in the application.
* Notifies listeners whenever the active scene changes.
*/
const SceneTracker = {
/**
* Sets the currently active scene and notifies all registered listeners.
* @param {Scene} scene - The new active scene object
*/
setActiveScene(scene: Scene) {
_activeScene = scene;
_listeners.forEach(listener => listener(_activeScene));
},

/**
* Gets the currently active scene.
* @returns {Scene} The active scene object
*/
getActiveScene(): Scene {
return _activeScene;
},

/**
* Registers a listener to be called whenever the active scene changes.
* @param {Function} callback - Function called with the new scene when it changes
* @returns {Object} Object with a remove() method to unsubscribe
*/
addActiveSceneChangedListener(callback: (scene: Scene) => void): {
remove: () => void,
...
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/**\n * Best Practices for Using Differ Functions\n * \n * 1. Remember the Return Convention\n * ❌ if (deepDiffer(a, b)) { /* a == b */ }\n * ✅ if (deepDiffer(a, b)) { /* a != b */ }\n * \n * 2. Use Specialized Differs When Possible\n * Prefer pointsDiffer over deepDiffer for points\n * Prefer sizesDiffer over deepDiffer for sizes\n * General rule: use most specific function for your data type\n * \n * 3. Be Careful with Max Depth\n * deepDiffer({a: {b: {c: 1}}}, {a: {b: {c: 2}}}, 2)\n * This won't detect the difference at c because depth is limited\n * \n * 4. Function Comparison Behavior\n * deepDiffer treats all functions as equal by default\n * This is intentional - function identity shouldn't trigger updates\n * \n * 5. Null/Undefined Handling\n * Most differs handle null/undefined gracefully\n * They use dummy objects to avoid crashes\n * \n * 6. Performance Notes\n * - Use differs in shouldComponentUpdate/useMemo\n * - Avoid deep nesting - consider flattening your data\n * - Array differences always trigger full array check\n * - Early exit on first difference found\n * \n * 7. Common Pitfalls\n * ❌ deepDiffer(prev, curr) === false means no change (WRONG)\n * ✅ deepDiffer(prev, curr) === false means no change (CORRECT)\n * ✅ !deepDiffer(prev, curr) means no change (CORRECT)\n */\n
52 changes: 52 additions & 0 deletions packages/react-native/Libraries/Utilities/differ/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Differ Utilities Module Documentation
*
* Purpose:
* --------
* Provides optimized equality comparison functions for React Native styling
* and layout calculations. These are specialized differ functions designed for
* high-performance use in the render loop where speed is critical.
*
* Return Value Convention:
* -----------------------
* All differ functions return:
* - true = values are DIFFERENT
* - false = values are EQUAL
*
* This is opposite to standard JS equality (===) and is optimized for
* React's shouldComponentUpdate pattern.
*
* Available Functions:
* -------------------
*
* deepDiffer(one, two, maxDepth?, options?)
* Deep recursive comparison for arbitrary objects/arrays
* - O(n) worst case, but short-circuits on first difference
* - Supports max depth limiting to avoid excessive recursion
* - Handles circular references gracefully
*
* matricesDiffer(one, two)
* Optimized for 4x4 transformation matrices
* - O(1) fixed size, checks indices by change likelihood
* - Critical for transform performance
*
* pointsDiffer(one, two)
* Compares {x, y} coordinate pairs
* - O(1) tiny allocation footprint
* - Null-safe with dummy object fallback
*
* sizesDiffer(one, two)
* Compares {width, height} dimensions
* - O(1) with graceful null handling
* - Commonly used in layout calculations
*
* insetsDiffer(one, two)
* Compares {top, left, right, bottom} spacing
* - O(1) for padding/margin comparisons
* - Null-safe with dummy fallback
*
* Performance Characteristics:
* ---------------------------
* All functions are allocation-free after initialization and suitable for
* use in hot paths like render methods and event handlers.
*/
36 changes: 34 additions & 2 deletions packages/react-native/Libraries/Utilities/differ/deepDiffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,18 @@ function unstable_setLogListeners(listeners: ?LogListeners) {
logListeners = listeners;
}

/*
* @returns {bool} true if different, false if equal
/**
* Deep equality comparison that recursively checks all properties.
*
* Returns true if values differ, false if equal. Handles primitives, objects,
* arrays, and mixed types. Functions are considered equal by default unless
* unsafelyIgnoreFunctions is set to false.
*
* @param {any} one - First value
* @param {any} two - Second value
* @param {Options|number} maxDepthOrOptions - Max recursion depth or options
* @param {Options} maybeOptions - Options when first is number
* @returns {boolean} True if different, false if equal
*/
function deepDiffer(
one: any,
Expand Down Expand Up @@ -102,3 +112,25 @@ function deepDiffer(

deepDiffer.unstable_setLogListeners = unstable_setLogListeners;
export default deepDiffer;

/**
* USAGE EXAMPLES:
*
* // Primitive comparison
* deepDiffer(1, 2); // true
* deepDiffer('hello', 'hello'); // false
*
* // Object comparison
* deepDiffer({a: 1}, {a: 1}); // false
* deepDiffer({a: 1, b: 2}, {a: 1}); // true
*
* // Array comparison
* deepDiffer([1, 2], [1, 2]); // false
* deepDiffer([1, 2], [1, 3]); // true
*
* // Nested structures
* deepDiffer({a: {b: [1, 2]}}, {a: {b: [1, 2]}}); // false
*
* // With depth limit
* deepDiffer({a: {b: {c: 1}}}, {a: {b: {c: 2}}}, 2); // false (depth limited)
*/
23 changes: 23 additions & 0 deletions packages/react-native/Libraries/Utilities/differ/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/

/**
* Collection of specialized comparison functions for React Native styling.
*
* These utilities provide optimized equality checks for common data types
* used in styling and layout calculations. They return true if values differ,
* false if equal - opposite of standard equality comparisons.
*/

export {default as deepDiffer} from './deepDiffer';
export {default as matricesDiffer} from './matricesDiffer';
export {default as pointsDiffer} from './pointsDiffer';
export {default as sizesDiffer} from './sizesDiffer';
export {default as insetsDiffer} from './insetsDiffer';
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ const dummyInsets = {
bottom: undefined,
};

/**
* Compares two inset objects for equality.
* Returns true if the insets are different, false if equal.
*
* @param {Inset} one - First inset object (top, left, right, bottom)
* @param {Inset} two - Second inset object
* @returns {boolean} True if insets differ, false if equal
* @performance O(1) - Compares only four numeric values
*/
function insetsDiffer(one: Inset, two: Inset): boolean {
one = one || dummyInsets;
two = two || dummyInsets;
Expand Down
16 changes: 10 additions & 6 deletions packages/react-native/Libraries/Utilities/differ/matricesDiffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@
'use strict';

/**
* Unrolls an array comparison specially for matrices. Prioritizes
* checking of indices that are most likely to change so that the comparison
* bails as early as possible.
* Unrolls an array comparison specially for matrices (4x4 transformation matrices).
*
* @param {MatrixMath.Matrix} one First matrix.
* @param {MatrixMath.Matrix} two Second matrix.
* @return {boolean} Whether or not the two matrices differ.
* Prioritizes checking of indices that are most likely to change so that the
* comparison bails as early as possible. This optimizes for common transformations.
*
* Index check order: [12,13,14,5,10,0,1,2,3,4,6,7,8,9,11,15]
*
* @param {?Array<number>} one - First matrix (16 elements)
* @param {?Array<number>} two - Second matrix (16 elements)
* @returns {boolean} True if matrices differ, false if equal
* @performance O(1) - Fixed size comparison, early exit on first difference
*/
function matricesDiffer(one: ?Array<number>, two: ?Array<number>): boolean {
if (one === two) {
Expand Down
10 changes: 10 additions & 0 deletions packages/react-native/Libraries/Utilities/differ/pointsDiffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,19 @@ type Point = {

const dummyPoint: Point = {x: undefined, y: undefined};

/**
* Compares two point objects for equality.
* Returns true if the points are different, false if equal.
*
* @param {?Point} one - First point object with x, y coordinates
* @param {?Point} two - Second point object with x, y coordinates
* @returns {boolean} True if points differ, false if equal
* @performance O(1) - Compares only two numeric values
*/
function pointsDiffer(one: ?Point, two: ?Point): boolean {
one = one || dummyPoint;
two = two || dummyPoint;
// Null-safe comparison: handles undefined values gracefully
return one !== two && (one.x !== two.x || one.y !== two.y);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
const dummySize = {width: undefined, height: undefined};
type Size = {width: ?number, height: ?number};

/**
* Compares two size objects for equality.
* Returns true if the sizes are different, false if equal.
*
* @param {Size} one - First size object with width, height
* @param {Size} two - Second size object with width, height
* @returns {boolean} True if sizes differ, false if equal
* @performance O(1) - Compares only two numeric values
*/
function sizesDiffer(one: Size, two: Size): boolean {
const defaultedOne = one || dummySize;
const defaultedTwo = two || dummySize;
Expand Down
15 changes: 13 additions & 2 deletions packages/react-native/Libraries/Utilities/dismissKeyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,24 @@
* @format
*/

// This function dismisses the currently-open keyboard, if any.

'use strict';

const TextInputState =
require('../Components/TextInput/TextInputState').default;

/**
* Programmatically dismisses the currently-open keyboard on the device.
*
* This function finds the currently focused text input and blurs it, which triggers
* the keyboard dismissal on iOS and Android.
*
* @example
* import { dismissKeyboard } from 'react-native';
* dismissKeyboard(); // Hide the keyboard
*
* @returns {void}
* @note This is a no-op if no text input is currently focused.
*/
function dismissKeyboard() {
TextInputState.blurTextInput(TextInputState.currentlyFocusedInput());
}
Expand Down
Loading
Loading