console.log in provider not printing - react-native

I have an expo app that loads a provider:
export const AppProvider = ({ children }: { children: ReactNode }) => {
console.log('Hello from app provider!!');
const alertsBottomSheetRef = useRef<BottomSheetModal>(null);
const dismissAlert = useCallback(() => {
alertsBottomSheetRef.current?.close();
}, []);
const values = {
alertsBottomSheetRef,
dismissAlert,
};
// >>>>> return <AppContext.Provider value={values}>{children}</AppContext.Provider>;
};
If I load the app with the last line commented, I can see the console.log. I can also see any changes I made to the console.log.
However, When I uncomment that last line, I don't get any console.logs.
Any thoughts why?

What I think why its not working because the AppContext.Provider is a context provider, and it does not log anything to the console. The purpose of a context provider is to provide data to components that are descendants of the provider. It does not render anything to the screen and does not log anything to the console.

The issue was that I started the server with expo start and not expo start --dev-client. console logs now appear as expected.

Related

Synchronization Problem - Try to log navigation and store the log by AsyncStorage

I am tring to log the navigation between screens by useEffect with using the useIsFocusd. I have customized that the logs store locally by AsyncStorage, but I found some logs have disappeared. I think it should be a synchronization problem, but async/await is not available in useEffect.
Any suggestions to my case?
let log=await AsyncStorage.getItem('log')
log=JSON.parse(log)
/Such as, ABC Screen navigated
log.push(props.msg+'\n')
/*I think the problem happens when there are multiple logs write at the same time. For example, 1 read 2 read, and 2 write 1 write. In this case, log 2 cannot be saved, but I dont know how to tackle it*/
await AsyncStorage.setItem('log',JSON.stringify(log));
I don‘t think that is the correct way to log the screen, you would have to implement logging for ich screen you wanna log.
In case you are using react navigation you can grab the screen in the NavigationContainer.
import {
NavigationContainer,
useNavigationContainerRef,
} from '#react-navigation/native';
export default () => {
const navigationRef = useNavigationContainerRef();
const routeNameRef = useRef();
return (
<NavigationContainer
ref={navigationRef}
onReady={() => {
routeNameRef.current = navigationRef.getCurrentRoute().name;
}}
onStateChange={async () => {
const previousRouteName = routeNameRef.current;
const currentRouteName = navigationRef.getCurrentRoute().name;
const trackScreenView = () => {
// Your implementation of analytics goes here!
};
if (previousRouteName !== currentRouteName) {
// Save the current route name for later comparison
routeNameRef.current = currentRouteName;
// Replace the line below to add the tracker from a mobile analytics SDK
await trackScreenView(currentRouteName);
}
}}
>
{/* ... */}
</NavigationContainer>
);
};
Explanation

You started loading the font "Poppins_400Regular" error

i am getting this error mutiple time
You started loading the font "Poppins_400Regular", but used it before it finished loading. You need to wait for Font.loadAsync to complete before using the font.
when run the code
In your apps entry point, usually App.jsx you can render null or a loading state whilst the fonts for your app load, and then once the loadAsync finishes you render your app, something along the lines of:
// App.jsx, or whatever your entry point is
const App = () => {
const [fontLoaded, setFontLoaded] = React.useState(false)
React.useEffect(() => {
Font.loadAsync({
"Poppins_400Regular": require("../path/to/your/font"),
})
.then(() => {
setFontLoaded(true)
})
}, [])
if (!fontLoaded) return null
return (
// All of your normal app ui
)
}

setState inside an async search

I am trying to set a stateful object with results I get from an async search and I'm getting some errors and strange behavior.
I'm using algoliasearch to run this search. Here's the code in pseudo, I can share the full code if needed, but I don't think it's important. The search works just fine and I get results (see comments below)
const [results, setResults] = useState(null);
index.search(searchQuery, {...requestOptions})
.then(({ hits }) => {
fetchedResultsArray = hits?.map((result) => {
...
return {...}
}
// console.log("fetchedResultsArray", fetchedResultsArray)
setResults(fetchedResultsArray)
).catch
It causing the app to act funny, get stuck, sometimes crash. Sometimes I get this (very long) error: Warning: Please report: Excessive number of pending callbacks: 501. Some pending callbacks that might have leaked by never being called from native code: {"1531":{"module":"NativeAnimatedModule","method":"startAnimatingNode"}...
When I comment setResults(fetchedResultsArray) and uncomment the console log before it, it prints the results and the app acts normal. Reverting back to setResults(fetchedResultsArray) and things go wrong again. I am doing something wrong/illegal, but what?
Another important thing to mention is that I'm using Expo SDK 41 beta 2.
UPDATE
Patrick, you pointed out what I am doing wrong. Let me show you in a little more depth what I do in my SearchScreen, again abbreviated:
const SearchScreen = () => {
// I get the initSearch function from a custom hook and you already see above what the function is.
const [
initSearch,
...
] = useSearch();
// Then I fire the search when the screen loaded
useLayoutEffect(() => {
if (region) {
initSearch({ searchQuery, region, filterOptions, priceOptions });
}
}, [filterOptions, initSearch, priceOptions, region, searchQuery]);
...
my custom hook is something like this:
export function useSearch() {
const [readyToAnimateAfterSearch, setReadyToAnimateAfterSearch] = useState<boolean>(false);
const [results, setResults] = useState<Result[] | null>(null);
const setAlert = useStore(useCallback((state) => state.setAlert, []));
const setProgressBarVisible = useStore(useCallback((state) => state.setProgressBarVisible, []));
const [radius, setRadius] = useState<number>(0);
const initSearch = useCallback(
async ({ searchQuery, region, filterOptions, priceOptions, loadMore = false }) => {
...
},
[results, setResults, setAlert, setProgressBarVisible]
);
return [
initSearch,
results,
setResults,
radius,
readyToAnimateAfterSearch,
setReadyToAnimateAfterSearch,
] as const;
Could you please suggest an alternative way to run this as an answer. No need to go into details, just so I get the flow of how you would handle this. Thank you!
My main issue was me using results inside the initSearch function, instead of using a stateUpdater function and removing results from the deps array.

React-navigation: Deep linking with authentication

I am building a mobile app with react-native and the react-navigation library for managing the navigation in my app. Right now, my app looks something like that:
App [SwitchNavigator]
Splash [Screen]
Auth [Screen]
MainApp [StackNavigator]
Home [Screen] (/home)
Profile [Screen] (/profile)
Notifications [Screen] (/notifications)
I have integrated Deep Linking with the patterns above for the screens Home, Profile and Notifications, and it works as expected. The issue I am facing is how to manage my user's authentication when using a deep link. Right now whenever I open a deep link (myapp://profile for instance) the app takes me on the screen whether or not I am authenticated. What I would want it to do is to check before in AsyncStorage if there is a userToken and if there isn't or it is not valid anymore then just redirect on the Auth screen.
I set up the authentication flow in almost exactly the same way as described here. So when my application starts the Splash screen checks in the user's phone if there is a valid token and sends him either on the Auth screen or Home screen.
The only solution I have come up with for now is to direct every deep link to Splash, authentify my user, and then parse the link to navigate to the good screen.
So for example when a user opens myapp://profile, I open the app on Splash, validate the token, then parse the url (/profile), and finally redirect either to Auth or Profile.
Is that the good way to do so, or does react-navigation provide a better way to do this ? The Deep linking page on their website is a little light.
Thanks for the help !
My setup is similar to yours. I followed Authentication flows · React Navigation and SplashScreen - Expo Documentation to set up my Auth flow, so I was a little disappointed that it was a challenge to get deep links to flow through it as well. I was able to get this working by customizing my main switch navigator, the approach is similar to what you stated was the solution you have for now. I just wanted to share my solution for this so there’s a concrete example of how it’s possible to get working. I have my main switch navigator set up like this (also I’m using TypeScript so ignore the type definitions if they are unfamiliar):
const MainNavigation = createSwitchNavigator(
{
SplashLoading,
Onboarding: OnboardingStackNavigator,
App: AppNavigator,
},
{
initialRouteName: 'SplashLoading',
}
);
const previousGetActionForPathAndParams =
MainNavigation.router.getActionForPathAndParams;
Object.assign(MainNavigation.router, {
getActionForPathAndParams(path: string, params: any) {
const isAuthLink = path.startsWith('auth-link');
if (isAuthLink) {
return NavigationActions.navigate({
routeName: 'SplashLoading',
params: { ...params, path },
});
}
return previousGetActionForPathAndParams(path, params);
},
});
export const AppNavigation = createAppContainer(MainNavigation);
Any deep link you want to route through your auth flow will need to start with auth-link, or whatever you choose to prepend it with. Here is what SplashLoading looks like:
export const SplashLoading = (props: NavigationScreenProps) => {
const [isSplashReady, setIsSplashReady] = useState(false);
const _cacheFonts: CacheFontsFn = fonts =>
fonts.map(font => Font.loadAsync(font as any));
const _cacheSplashAssets = () => {
const splashIcon = require(splashIconPath);
return Asset.fromModule(splashIcon).downloadAsync();
};
const _cacheAppAssets = async () => {
SplashScreen.hide();
const fontAssetPromises = _cacheFonts(fontMap);
return Promise.all([...fontAssetPromises]);
};
const _initializeApp = async () => {
// Cache assets
await _cacheAppAssets();
// Check if user is logged in
const sessionId = await SecureStore.getItemAsync(CCSID_KEY);
// Get deep linking params
const params = props.navigation.state.params;
let action: any;
if (params && params.routeName) {
const { routeName, ...routeParams } = params;
action = NavigationActions.navigate({ routeName, params: routeParams });
}
// If not logged in, navigate to Auth flow
if (!sessionId) {
return props.navigation.dispatch(
NavigationActions.navigate({
routeName: 'Onboarding',
action,
})
);
}
// Otherwise, navigate to App flow
return props.navigation.navigate(
NavigationActions.navigate({
routeName: 'App',
action,
})
);
};
if (!isSplashReady) {
return (
<AppLoading
startAsync={_cacheSplashAssets}
onFinish={() => setIsSplashReady(true)}
onError={console.warn}
autoHideSplash={false}
/>
);
}
return (
<View style={{ flex: 1 }}>
<Image source={require(splashIconPath)} onLoad={_initializeApp} />
</View>
);
};
I create the deep link with a routeName query param, which is the name of the screen to navigate to after the auth check has been performed (you can obviously add whatever other query params you need). Since my SplashLoading screen handles loading all fonts/assets as well as auth checking, I need every deep link to route through it. I was facing the issue where I would manually quit the app from multitasking, tap a deep link url, and have the app crash because the deep link bypassed SplashLoading so fonts weren’t loaded.
The approach above declares an action variable, which if not set will do nothing. If the routeName query param is not undefined, I set the action variable. This makes it so once the Switch router decides which path to take based on auth (Onboarding or App), that route gets the child action and navigates to the routeName after exiting the auth/splash loading flow.
Here’s an example link I created that is working fine with this system:
exp://192.168.1.7:19000/--/auth-link?routeName=ForgotPasswordChange&cacheKey=a9b3ra50-5fc2-4er7-b4e7-0d6c0925c536
Hopefully the library authors will make this a natively supported feature in the future so the hacks aren’t necessary. I'd love to see what you came up with as well!
On my side I achieved this without having to manually parse the route to extract path & params.
Here are the steps:
getting the navigation action returned by: getActionForPathAndParams
passing the navigation action to the Authentication view as param
then when the authentication succeed or if the authentication is already ok I dispatch the navigation action to go on the intended route
const previousGetActionForPathAndParams = AppContainer.router.getActionForPathAndParams
Object.assign(AppContainer.router, {
getActionForPathAndParams(path: string, params: NavigationParams) {
const navigationAction = previousGetActionForPathAndParams(path, params)
return NavigationActions.navigate({
routeName: 'Authentication',
params: { navigationAction }
})
}
})
Then In the Authentication view:
const navigationAction = this.navigation.getParam('navigationAction')
if (navigationAction)
this.props.navigation.dispatch(navigationAction)
I ended up using a custom URI to intercept the deeplink launch, and then passing those params to the intended route. My loading screen handles the auth check.
const previousGetActionForPathAndParams = AppContainer.router.getActionForPathAndParams
Object.assign(AppContainer.router, {
getActionForPathAndParams (path, params) {
if (path === 'auth' && params.routeName && params.userId ) {
// returns a profile navigate action for myApp://auth?routeName=chat&userId=1234
return NavigationActions.navigate({
routeName: 'Loading',
params: { ...params, path },
})
}
return previousGetActionForPathAndParams(path, params)
},
})
https://reactnavigation.org/docs/en/routers.html#handling-custom-uris
Then, in my Loading Route, I parse the params like normal but then route to the intended location, passing them once again.
const userId = this.props.navigation.getParam('userId')
https://reactnavigation.org/docs/en/params.html
I found an easier way, i'm maintaining the linking in a separate file and importing it in the main App.js
linking.js
const config = {
screens: {
Home:'home',
Profile:'profile,
},
};
const linking = {
prefixes: ['demo://app'],
config,
};
export default linking;
App.js
& during login I keep the token inside async storage and when user logs out the token is deleted. Based on the availability of token i'm attaching the linking to navigation and detaching it using state & when its detached it falls-back to SplashScreen.
Make sure to set initialRouteName="SplashScreen"
import React, {useState, useEffect} from 'react';
import {Linking} from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
import {createStackNavigator} from '#react-navigation/stack';
import {NavigationContainer} from '#react-navigation/native';
import linking from './utils/linking';
import {Home, Profile, SplashScreen} from './components';
const Stack = createStackNavigator();
// This will be used to retrieve the AsyncStorage String value
const getData = async (key) => {
try {
const value = await AsyncStorage.getItem(key);
return value != null ? value : '';
} catch (error) {
console.error(`Error Caught while getting async storage data: ${error}`);
}
};
function _handleOpenUrl(event) {
console.log('handleOpenUrl', event.url);
}
const App = () => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
// Checks if the user is logged in or not, if not logged in then
// the app prevents the access to deep link & falls back to splash screen.
getData('studentToken').then((token) => {
if (token === '' || token === undefined) setIsLoggedIn(false);
else setIsLoggedIn(true);
});
Linking.addEventListener('url', _handleOpenUrl);
return () => {
Linking.removeEventListener('url', _handleOpenUrl);
};
}, []);
return (
//linking is enabled only if the user is logged in
<NavigationContainer linking={isLoggedIn && linking}>
<Stack.Navigator
initialRouteName="SplashScreen"
screenOptions={{...TransitionPresets.SlideFromRightIOS}}>
<Stack.Screen
name="SplashScreen"
component={SplashScreen}
options={{headerShown: false}}
/>
<Stack.Screen
name="Home"
component={Home}
options={{headerShown: false, gestureEnabled: false}}
/>
<Stack.Screen
name="Profile"
component={Profile}
options={{headerShown: false, gestureEnabled: false}}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
When a logged in user opens the deep link from notification then it will take him to the respective deep linked screen, if he's not logged in then it will open from splash screen.
I created a package for Auto Deep linking with Authentication Flow.
You can try it. auth-linking https://github.com/SohelIslamImran/auth-linking
auth-linking
Auto Deep linking with Authentication Flow
Deep linking is very easy to use with authentication. But some people take it hard way. So this package will help you to achieve the easiest way to handle Deep linking with Authentication Flow.
Installation
npm install auth-linking
yarn add auth-linking
Usage
AuthLinkingProvider
You need to wrap your app with AuthLinkingProvider.
import AuthLinkingProvider from "auth-linking";
...
const App = () => {
return (
<AuthLinkingProvider onAuthChange={onAuthChange}>
{/* Your app components */}
</AuthLinkingProvider>
);
};
export default App;
onAuthChange prop
You need to provide an onAuthChange prop to AuthLinkingProvider. This a function that should return a promise with the user or truthy value (if logged in) and null or falsy (if the user is not logged in).
const onAuthChange = () => {
return new Promise((resolve, reject) => {
onAuthStateChanged(auth, resolve, reject);
});
};
...
<AuthLinkingProvider onAuthChange={onAuthChange}>
useAutoRedirectToDeepLink
Call this hook inside a screen that will render after all auth flow is completed. So this hook will automatically redirect to the deep link through which the app is opened.
import { useAutoRedirectToDeepLink } from "auth-linking";
...
const Home = () => {
useAutoRedirectToDeepLink()
return (
<View>{...}</View>
);
};
All done.

Reset app or state in React Native

I'm working with an app that was handed to us that is fairly big, and im very new to React Native.
I'm not sure that resetting will solve it. But I think I somehow polluted my state. Everything worked fine but then I wanted to reset my state, so I found this in app.js
componentWillMount() {
let persistore = persistStore(store, null, () => {
this.setState({rehydrated: true})
});
// persistore.purge();
}
So I uncommented persistore.purge();. But now when I try to start it I get:
Unhandled promise rejection: TypeError: null is not an object (evaluating 'instanceInfo.pcb')
And it wont stop stageing. Any ideas on what I can try to do. To "reset" what I have done? I have already reseted to a stable commit in git.
I don't understand your question resetting state means the whole store i.e. app state?
If so then
const appReducer = combineReducers({
nav,
listReducer,
movieDetail
});
const initialState = appReducer({}, {});
const rootReducer = (state, action) => {
if(action.type == "RESET_APP_STATE") {
state = undefined
}
return appReducer(state, action);
}
If you want particular state i.e reducer state same thing but only for that particular reducer in switch mostly what we use, return undefined