Skip to content

Commit

Permalink
Merge pull request Expensify#32484 from JKobrynski/migrateRemainingHo…
Browse files Browse the repository at this point in the history
…oksToTypeScript

[TS Migration] Migrate '[Remaining Group 2]' hook to TypeScript
  • Loading branch information
tgolen authored Dec 13, 2023
2 parents d6755d6 + b620ee8 commit 2ec041d
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 23 deletions.
17 changes: 11 additions & 6 deletions src/hooks/useAutoFocusInput.js → src/hooks/useAutoFocusInput.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import {useFocusEffect} from '@react-navigation/native';
import {useCallback, useContext, useEffect, useRef, useState} from 'react';
import {InteractionManager} from 'react-native';
import {InteractionManager, TextInput} from 'react-native';
import CONST from '@src/CONST';
import * as Expensify from '@src/Expensify';

export default function useAutoFocusInput() {
type UseAutoFocusInput = {
inputCallbackRef: (ref: TextInput | null) => void;
};

export default function useAutoFocusInput(): UseAutoFocusInput {
const [isInputInitialized, setIsInputInitialized] = useState(false);
const [isScreenTransitionEnded, setIsScreenTransitionEnded] = useState(false);

// @ts-expect-error TODO: Remove this when Expensify.js is migrated.
const {isSplashHidden} = useContext(Expensify.SplashScreenHiddenContext);

const inputRef = useRef(null);
const focusTimeoutRef = useRef(null);
const inputRef = useRef<TextInput | null>(null);
const focusTimeoutRef = useRef<NodeJS.Timeout | null>(null);

useEffect(() => {
if (!isScreenTransitionEnded || !isInputInitialized || !inputRef.current || !isSplashHidden) {
return;
}
InteractionManager.runAfterInteractions(() => {
inputRef.current.focus();
inputRef.current?.focus();
setIsScreenTransitionEnded(false);
});
}, [isScreenTransitionEnded, isInputInitialized, isSplashHidden]);
Expand All @@ -38,7 +43,7 @@ export default function useAutoFocusInput() {
}, []),
);

const inputCallbackRef = (ref) => {
const inputCallbackRef = (ref: TextInput | null) => {
inputRef.current = ref;
setIsInputInitialized(true);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import {useFocusEffect} from '@react-navigation/native';
import {useCallback, useRef} from 'react';
import {MutableRefObject, useCallback, useRef} from 'react';
import {TextInput} from 'react-native';
import CONST from '@src/CONST';

/**
* Focus a text input when a screen is navigated to, after the specified time delay has elapsed.
*
* @param {Object} inputRef
* @param {Number} [delay]
*/
export default function useDelayedInputFocus(inputRef, delay = CONST.ANIMATED_TRANSITION) {
const timeoutRef = useRef(null);
export default function useDelayedInputFocus(inputRef: MutableRefObject<TextInput>, delay: number = CONST.ANIMATED_TRANSITION) {
const timeoutRef = useRef<NodeJS.Timeout | null>(null);

useFocusEffect(
useCallback(() => {
timeoutRef.current = setTimeout(() => inputRef.current && inputRef.current.focus(), delay);
timeoutRef.current = setTimeout(() => inputRef.current?.focus(), delay);
return () => {
if (!timeoutRef.current) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
// eslint-disable-next-line no-restricted-imports
import {useEffect, useState} from 'react';
import {Dimensions} from 'react-native';
import {Dimensions, type ScaledSize} from 'react-native';

type InitialWindowDimensions = {
initialWidth: number;
initialHeight: number;
};

type NewDimensions = {
window: ScaledSize;
screen: ScaledSize;
};

/**
* A convenience hook that provides initial size (width and height).
* An initial height allows to know the real height of window,
* while the standard useWindowDimensions hook return the height minus Virtual keyboard height
* @returns {Object} with information about initial width and height
* @returns with information about initial width and height
*/
export default function () {
export default function (): InitialWindowDimensions {
const [dimensions, setDimensions] = useState(() => {
const window = Dimensions.get('window');
const screen = Dimensions.get('screen');
Expand All @@ -22,7 +32,7 @@ export default function () {
});

useEffect(() => {
const onDimensionChange = (newDimensions) => {
const onDimensionChange = (newDimensions: NewDimensions) => {
const {window, screen} = newDimensions;

setDimensions((oldState) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import {useTabAnimation} from '@react-navigation/material-top-tabs';
import {useIsFocused} from '@react-navigation/native';
import {useEffect, useState} from 'react';
import {Animated} from 'react-native';
import DomUtils from '@libs/DomUtils';

type UseTabNavigatorFocusParams = {
tabIndex: number;
};

type PositionAnimationListenerCallback = {
value: number;
};

/**
* Custom React hook to determine the focus status of a tab in a Material Top Tab Navigator.
* It evaluates whether the current tab is focused based on the tab's animation position and
Expand All @@ -17,15 +26,16 @@ import DomUtils from '@libs/DomUtils';
* might not be used within a Material Top Tabs Navigator context. Proper usage should ensure that
* this hook is only used where appropriate.
*
* @param {Object} params - The parameters object.
* @param {Number} params.tabIndex - The index of the tab for which focus status is being determined.
* @returns {Boolean} Returns `true` if the tab is both animation-focused and screen-focused, otherwise `false`.
* @param params - The parameters object.
* @param params.tabIndex - The index of the tab for which focus status is being determined.
* @returns Returns `true` if the tab is both animation-focused and screen-focused, otherwise `false`.
*
* @example
* const isTabFocused = useTabNavigatorFocus({ tabIndex: 1 });
*/
function useTabNavigatorFocus({tabIndex}) {
let tabPositionAnimation = null;
function useTabNavigatorFocus({tabIndex}: UseTabNavigatorFocusParams): boolean {
let tabPositionAnimation: Animated.AnimatedInterpolation<number> | null = null;

try {
// Retrieve the animation value from the tab navigator, which ranges from 0 to the total number of pages displayed.
// Even a minimal scroll towards the camera page (e.g., a value of 0.001 at start) should activate the camera for immediate responsiveness.
Expand All @@ -46,7 +56,7 @@ function useTabNavigatorFocus({tabIndex}) {
}
const index = Number(tabIndex);

const listenerId = tabPositionAnimation.addListener(({value}) => {
const listenerId = tabPositionAnimation.addListener(({value}: PositionAnimationListenerCallback) => {
// Activate camera as soon the index is animating towards the `tabIndex`
DomUtils.requestAnimationFrame(() => {
setIsTabFocused(value > index - 1 && value < index + 1);
Expand All @@ -56,6 +66,7 @@ function useTabNavigatorFocus({tabIndex}) {
// We need to get the position animation value on component initialization to determine
// if the tab is focused or not. Since it's an Animated.Value the only synchronous way
// to retrieve the value is to use a private method.
// @ts-expect-error -- __getValue is a private method
// eslint-disable-next-line no-underscore-dangle
const initialTabPositionValue = tabPositionAnimation.__getValue();

Expand Down
8 changes: 8 additions & 0 deletions src/types/modules/material-top-tabs.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** This direct import is required, because this function was added by a patch,
* and its typings are not supported by default */
import {useTabAnimation} from '@react-navigation/material-top-tabs/src/utils/useTabAnimation';

declare module '@react-navigation/material-top-tabs' {
// eslint-disable-next-line import/prefer-default-export
export {useTabAnimation};
}

0 comments on commit 2ec041d

Please sign in to comment.