mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2025-12-23 22:18:36 -05:00
fix: prevent iconic taxon common names from disappearing in the local db (#2631)
Ensure useAuthenticatedQuery only executes the query when it knows if the user is signed in or not, and appends that state to the query key so signed in and signed out results are distinct. The problem here was that useIconicTaxa was mysteriously returning stale, signed out results while offline. Closes MOB-383
This commit is contained in:
@@ -1531,7 +1531,7 @@ SPEC CHECKSUMS:
|
||||
MMKV: f7d1d5945c8765f97f39c3d121f353d46735d801
|
||||
MMKVCore: c04b296010fcb1d1638f2c69405096aac12f6390
|
||||
Mute: 20135a96076f140cc82bfc8b810e2d6150d8ec7e
|
||||
RCT-Folly: cd21f1661364f975ae76b3308167ad66b09f53f5
|
||||
RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0
|
||||
RCTRequired: 77f73950d15b8c1a2b48ba5b79020c3003d1c9b5
|
||||
RCTTypeSafety: ede1e2576424d89471ef553b2aed09fbbcc038e3
|
||||
React: 2ddb437e599df2f1bffa9b248de2de4cfa0227f0
|
||||
|
||||
@@ -1,29 +1,56 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getJWT } from "components/LoginSignUp/AuthenticationService.ts";
|
||||
import { getJWT, isLoggedIn } from "components/LoginSignUp/AuthenticationService.ts";
|
||||
import { useEffect, useState } from "react";
|
||||
import { reactQueryRetry } from "sharedHelpers/logging";
|
||||
|
||||
const LOGGED_IN_UNKNOWN = null;
|
||||
|
||||
// Should work like React Query's useQuery except it calls the queryFunction
|
||||
// with an object that includes the JWT
|
||||
const useAuthenticatedQuery = (
|
||||
queryKey,
|
||||
queryFunction,
|
||||
queryOptions = {}
|
||||
) => useQuery( {
|
||||
queryKey: [...queryKey, queryOptions.allowAnonymousJWT],
|
||||
queryFn: async ( ) => {
|
||||
// Note, getJWT() takes care of fetching a new token if the existing
|
||||
// one is expired. We *could* store the token in state with useState if
|
||||
// fetching from RNSInfo becomes a performance issue
|
||||
const apiToken = await getJWT( queryOptions.allowAnonymousJWT );
|
||||
const options = {
|
||||
api_token: apiToken
|
||||
};
|
||||
return queryFunction( options );
|
||||
},
|
||||
retry: ( failureCount, error ) => reactQueryRetry( failureCount, error, {
|
||||
queryKey
|
||||
} ),
|
||||
...queryOptions
|
||||
} );
|
||||
) => {
|
||||
const [userLoggedIn, setUserLoggedIn] = useState( LOGGED_IN_UNKNOWN );
|
||||
|
||||
// Whether we perform this query and whether we need to re-perform it
|
||||
// depends on whether the user is signed in. The possible vulnerability
|
||||
// here is that this effect might not run frequently enough to change when
|
||||
// a user signs in or out. The reason we're not using useCurrentUser is it
|
||||
// doesn't tell us whether we know the user's auth state yet, it only
|
||||
// returns null when we don't know OR the user is signed out.
|
||||
useEffect( ( ) => {
|
||||
isLoggedIn()
|
||||
.then( result => setUserLoggedIn( result ) )
|
||||
.catch( ( ) => setUserLoggedIn( LOGGED_IN_UNKNOWN ) );
|
||||
}, [queryKey, queryOptions] );
|
||||
|
||||
// The results will probably be different depending on whether the user is
|
||||
// signed in or we wouldn't be using useAuthenticatedQuery in the first
|
||||
// place, so we need to redo this request if the auth state changed
|
||||
const authQueryKey = [...queryKey, queryOptions.allowAnonymousJWT, userLoggedIn];
|
||||
|
||||
return useQuery( {
|
||||
queryKey: authQueryKey,
|
||||
queryFn: async ( ) => {
|
||||
// Note, getJWT() takes care of fetching a new token if the existing
|
||||
// one is expired. We *could* store the token in state with useState if
|
||||
// fetching from RNSInfo becomes a performance issue
|
||||
const apiToken = await getJWT( queryOptions.allowAnonymousJWT );
|
||||
const options = {
|
||||
api_token: apiToken
|
||||
};
|
||||
return queryFunction( options );
|
||||
},
|
||||
retry: ( failureCount, error ) => reactQueryRetry( failureCount, error, {
|
||||
queryKey
|
||||
} ),
|
||||
...queryOptions,
|
||||
// Authenticated queries should not run until we know whether or not the
|
||||
// user is signed in
|
||||
enabled: userLoggedIn !== LOGGED_IN_UNKNOWN && queryOptions.enabled
|
||||
} );
|
||||
};
|
||||
|
||||
export default useAuthenticatedQuery;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { searchTaxa } from "api/taxa";
|
||||
import type { ApiOpts, ApiTaxon } from "api/types";
|
||||
import { RealmContext } from "providers/contexts.ts";
|
||||
import { useEffect, useState } from "react";
|
||||
import { UpdateMode } from "realm";
|
||||
@@ -14,12 +15,10 @@ const useIconicTaxa = ( options: { reload: boolean } = { reload: false } ) => {
|
||||
const [isUpdatingRealm, setIsUpdatingRealm] = useState<boolean>( );
|
||||
const enabled = !!( reload );
|
||||
|
||||
const queryKey = ["searchTaxa", reload];
|
||||
const queryKey = ["useIconicTaxa", reload];
|
||||
const { data: iconicTaxa } = useAuthenticatedQuery(
|
||||
queryKey,
|
||||
optsWithAuth => searchTaxa( {
|
||||
iconic: true
|
||||
}, optsWithAuth ),
|
||||
( optsWithAuth: ApiOpts ) => searchTaxa( { iconic: true }, optsWithAuth ),
|
||||
{ enabled }
|
||||
);
|
||||
|
||||
@@ -27,7 +26,7 @@ const useIconicTaxa = ( options: { reload: boolean } = { reload: false } ) => {
|
||||
if ( iconicTaxa?.length > 0 && !isUpdatingRealm ) {
|
||||
setIsUpdatingRealm( true );
|
||||
safeRealmWrite( realm, ( ) => {
|
||||
iconicTaxa.forEach( taxon => {
|
||||
iconicTaxa.forEach( ( taxon: ApiTaxon ) => {
|
||||
realm.create(
|
||||
"Taxon",
|
||||
Taxon.forUpdate( taxon, { isIconic: true } ),
|
||||
|
||||
@@ -24,9 +24,12 @@ function saveTaxaToRealm( taxa: Taxon[], realm: Realm ) {
|
||||
}, "saving remote taxon from useTaxonSearch" );
|
||||
}
|
||||
|
||||
const useTaxonSearch = ( taxonQuery = "" ) => {
|
||||
const useTaxonSearch = ( taxonQueryArg = "" ) => {
|
||||
const realm = useRealm( );
|
||||
const iconicTaxa = useIconicTaxa( { reload: false } );
|
||||
// Remove leading and trailing whitespace, no need to perform new queries or
|
||||
// potentially get different results b/c of meaningless whitespace
|
||||
const taxonQuery = taxonQueryArg.trim();
|
||||
|
||||
const { data: remoteTaxa, refetch, isLoading } = useAuthenticatedQuery(
|
||||
["fetchTaxonSuggestions", taxonQuery],
|
||||
|
||||
@@ -158,10 +158,10 @@ describe( "TaxonDetails", ( ) => {
|
||||
await actor.type( searchBar, "b" );
|
||||
|
||||
const searchedTaxon = mockTaxaList[0];
|
||||
const searchedTaxonName = await screen.findByText( searchedTaxon.name );
|
||||
await waitFor( ( ) => {
|
||||
expect( searchedTaxonName ).toBeVisible( );
|
||||
await waitFor( async ( ) => {
|
||||
expect( await screen.findByText( searchedTaxon.name ) ).toBeVisible( );
|
||||
} );
|
||||
const searchedTaxonName = await screen.findByText( searchedTaxon.name );
|
||||
await actor.press( searchedTaxonName );
|
||||
|
||||
const taxonDetailsScreen = await screen.findByTestId( `TaxonDetails.${searchedTaxon.id}` );
|
||||
|
||||
@@ -115,7 +115,7 @@ describe( "when there is no local taxon with taxon id", ( ) => {
|
||||
|
||||
it( "should return a taxon like a local taxon record if the request succeeds", async ( ) => {
|
||||
const { result } = renderHook( ( ) => useTaxon( { id: mockTaxon.id } ) );
|
||||
expect( result.current.taxon ).toHaveProperty( "default_photo" );
|
||||
await waitFor( ( ) => expect( result.current.taxon ).toHaveProperty( "default_photo" ) );
|
||||
} );
|
||||
} );
|
||||
|
||||
|
||||
Reference in New Issue
Block a user