Migrate SuggestionsResult to TypeScript (#2901)

* Rename file

* Use PressableProps interface for PressableWithTracking

* Remove TODO

* Update handleLayout types

* Update allowed prop type

If SuggestionsResult can have ApiTaxon | RealmTaxon, we have to enable it in DisplayTaxonName as well, and rank_level can be undefined according to this union.

* Allow null

accessibleTaxonName uses currentUser directly from useCurrentUser which can be null.

* Use as to declare which type is used

* Source might be null
This commit is contained in:
Johannes Klein
2025-05-16 14:57:25 +02:00
committed by GitHub
parent bb2c549625
commit ed3f0e982c
6 changed files with 35 additions and 30 deletions

View File

@@ -7,6 +7,7 @@ import {
} from "components/SharedComponents";
import { Pressable, View } from "components/styledComponents";
import React, { useEffect, useRef } from "react";
import type { LayoutChangeEvent } from "react-native";
import type { RealmTaxon } from "realmModels/types";
import { accessibleTaxonName } from "sharedHelpers/taxon.ts";
import {
@@ -18,7 +19,7 @@ type Props = {
handlePress?: ( ) => void,
taxon: RealmTaxon | ApiTaxon,
testID?: string,
updateMaxHeight?: ( ) => void,
updateMaxHeight?: ( height: number ) => void,
forcedHeight: number
}
@@ -53,14 +54,15 @@ const SuggestionsResult = ( {
// A representative photo is dependant on the actual image that was scored by computer vision
// and is currently not added to the taxon realm. So, if it is available directly from the
// suggestion, i.e. taxonProp, use it. Otherwise, use the default photo from the taxon.
const taxonImage = {
uri: taxon?.representative_photo?.url
|| taxon?.default_photo?.url
|| taxon?.defaultPhoto?.url
};
const uri = ( taxon as ApiTaxon )?.representative_photo?.url
|| ( taxon as ApiTaxon )?.default_photo?.url
|| ( taxon as RealmTaxon )?.defaultPhoto?.url;
const taxonImage = uri
? { uri }
: undefined;
// Handle the onLayout event to measure item height
const handleLayout = event => {
const handleLayout = ( event: LayoutChangeEvent ) => {
const { height } = event.nativeEvent.layout;
// Only report height once to avoid infinite loops
if ( updateMaxHeight && height > 0 && !measuredRef.current ) {
@@ -104,7 +106,6 @@ const SuggestionsResult = ( {
: undefined}
>
<ObsImagePreview
// TODO fix when ObsImagePreview typed
source={taxonImage}
testID={`${testID}.photo`}
iconicTaxonName={taxon?.iconic_taxon_name}

View File

@@ -25,7 +25,7 @@ interface Props extends PropsWithChildren {
opaque?: boolean;
selectable?: boolean;
selected?: boolean;
source: {
source?: {
uri: string;
};
style?: ViewStyle;

View File

@@ -1,26 +1,29 @@
import { getCurrentRoute } from "navigation/navigationUtils.ts";
import React from "react";
import type { PressableProps } from "react-native";
import { GestureResponderEvent, Pressable } from "react-native";
import { log } from "sharedHelpers/logger";
const logger = log.extend( "PressableWithTracking" );
const PressableWithTracking = React.forwardRef( ( props, ref ) => {
const { onPress, ...otherProps } = props;
const PressableWithTracking = React.forwardRef<typeof Pressable, PressableProps>(
( props, ref ) => {
const { onPress, ...otherProps } = props;
const handlePressWithTracking = ( event?: GestureResponderEvent ) => {
if ( otherProps?.testID ) {
const currentRoute = getCurrentRoute( );
logger.info( `Button tap: ${otherProps?.testID}-${currentRoute?.name || "undefined"}` );
}
const handlePressWithTracking = ( event: GestureResponderEvent ) => {
if ( otherProps?.testID ) {
const currentRoute = getCurrentRoute( );
logger.info( `Button tap: ${otherProps?.testID}-${currentRoute?.name || "undefined"}` );
}
if ( onPress ) {
onPress( event );
}
};
if ( onPress ) {
onPress( event );
}
};
// eslint-disable-next-line react/jsx-props-no-spreading
return <Pressable {...otherProps} onPress={handlePressWithTracking} ref={ref} />;
} );
// eslint-disable-next-line react/jsx-props-no-spreading
return <Pressable {...otherProps} onPress={handlePressWithTracking} ref={ref} />;
}
);
export default PressableWithTracking;

View File

@@ -1,3 +1,4 @@
import type { ApiTaxon } from "api/types";
import classnames from "classnames";
import {
Body1, Body3, Body4
@@ -33,7 +34,7 @@ interface Props {
showOneNameOnly?: boolean;
selectable?: boolean;
small?: boolean;
taxon: RealmTaxon;
taxon: RealmTaxon | ApiTaxon;
textCentered?: boolean;
topTextComponent?: React.ComponentType<TextProps>;
underlineTopText?: boolean;

View File

@@ -14,7 +14,7 @@ interface Props {
isTitle?: boolean;
keyBase: string;
rank?: string;
rankLevel: number;
rankLevel?: number;
rankPiece?: string;
scientificNamePieces?: string[];
taxonId: string | number;
@@ -38,7 +38,7 @@ const ScientificName = ( {
}: Props ) => {
const { t } = useTranslation( );
const scientificNameArray = scientificNamePieces?.map( ( piece, index ) => {
const isItalics = piece !== rankPiece && (
const isItalics = piece !== rankPiece && rankLevel && (
rankLevel <= Taxon.SPECIES_LEVEL || rankLevel === Taxon.GENUS_LEVEL
);
const spaceChar = ( ( index !== scientificNamePieces.length - 1 ) || isHorizontal )
@@ -67,7 +67,7 @@ const ScientificName = ( {
);
} );
if ( rank && rankLevel > Taxon.SPECIES_LEVEL ) {
if ( rank && rankLevel && rankLevel > Taxon.SPECIES_LEVEL ) {
scientificNameArray.unshift( " " );
scientificNameArray.unshift( translatedRank( rank, t ) );
}

View File

@@ -99,14 +99,14 @@ export const capitalizeCommonName = ( name: string ) => {
interface Taxon {
rank?: string;
rank_level: number;
rank_level?: number;
preferred_common_name?: string;
name?: string;
}
interface TaxonDisplayData {
rank?: string;
rankLevel: number;
rankLevel?: number;
commonName?: string;
rankPiece?: string;
scientificNamePieces?: string[];
@@ -160,7 +160,7 @@ interface User {
}
export function accessibleTaxonName(
taxon: Taxon,
user: User,
user: User | null,
t: ( key: string, options: {} ) => string
) {
const { commonName, scientificName } = generateTaxonPieces( taxon );