I use react-native with apollo-client3.
In my App.js file that starts my project, I configure if I log in go to LoggedInNav else LoggedOutNav.
<ApolloProvider client={client}>
<NavigationContainer>
<View onLayout={onLayoutRootView}></View>
{isLoggedIn ? <LoggedInNav /> : <LoggedoutNav />}
</NavigationContainer>
</ApolloProvider>
And the state whether I log in or not is tracked from useReactiveVar.
const isLoggedIn = useReactiveVar(isLoggedInVar);
In my LogIn.js file, I do log in and if it succeeds, I update my isLoggedInVar state made from makeVar of apollo/client from false to true.
export const isLoggedInVar = makeVar(false);
export const tokenVar = makeVar("");
export const logUserIn = async (token) => {
try {
await AsyncStorage.setItem("token", JSON.stringify(token));
isLoggedInVar(true);
tokenVar(token);
} catch (e) {
console.error(e);
}
};
So I update my state of makeVar in another screen, but can App.js listen to this update and navigate from <LoggedInNav/> to <LoggedOutNav/>?
I think App.js file is the bottom of all screens of my app, it should work.
But in my app project, I feel sometimes it might works but sometimes it throws Network error.
And in this case How can I make switch between two Navs according to Login state? Please help me.
Related
I have currently implemented the sign out functionality on my app with (using firebase authentication):
const handleSignOut = () => {
signOut(auth)
.then(() => navigation.navigate("Login"))
.catch((err) => console.error(err));
};
However, I understand this isn't enough as I notice that some state is being preserved accross users (state is preserved even after logout), and I was wondering what the best way to clear/reset all the state is.
Do I need to manually unmount all my components (I still don't quite understand how/when component unmounting happens), or is there an easy way I can achieve this using navigation or context tools?
I am using the Context API and React Navigation:
const Stack = createStackNavigator();
const Navigation = () => {
return (
<NavigationContainer>
<Stack.Navigator
headerMode="none"
>
<Stack.Screen
name="Register"
component={RegisterScreen}
/>
<Stack.Screen
name="Login"
component={LoginScreen}
/>
<Stack.Screen
name="Home"
component={HomeScreen}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
It's hard to say because a lot of this is application specific, but generally speaking, you need to dedicate a single method which is capable destroying a user's session, and making sure that's persistent. Maybe it's logging out of Firebase, maybe it's clearing some cookies, maybe it's updating some other persistent state.
The point is, you write a single method which is responsible for performing all of the data actions to make sure that session doesn't exist any more. Importantly, it's not associated with any navigation or whatever. It's just the pure data operations.
Then you have this reusable function to log the user out that can be called from wherever. Applied to your example...
// Notice that things that can possibly fail are wrapped in dedicated try/catches.
// You don't want the failure to clear some state prevent your ability to clear others.
const doSomeBigGlobalSessionLogout = async () => {
const maybeErrors = [];
try {
await firebase.signOut(auth);
} catch (e) {
maybeErrors.push(e);
}
try {
await clearSomeOtherPersistentState();
} catch (e) {
maybeErrors.push(e);
}
/* do something with errors */
// for (error of maybeErrors) { ... }
// Still resolve successfully, because you won't want to lock the user from
// not being able to sign out.
return Promise.resolve();
};
// Here you can trust it will never lock the user out, and will always clear state as best as it can.
doSomeBigGlobalSessionLogout()
// You *will* get here.
.then(() => navigation.navigate("Login"));
Finally in terms of the UI, it would probably make sense to move the user first. Otherwise the user would see their state disappear from the screen before their eyes!
Promise.resolve()
.then(() => navigation.navigate("SomePlaceThatsPretty"));
.then(doSomeBigGlobalSessionLogout)
.then(() => navigation.navigate("Login"));
I have a react native app and whenever I try to login or logout the app crashes .. I am not getting any error logs in React Native Debugger.
so the App Crashes and then when I reopen the app the changes have already taken effect ( i.e :- If I was trying to login I would be logged in when I reopen the app)
I have 2 files for Navigation
CofficNavigator.tsx
// This file has all the routing
// here BottomNavigator is a tab Navigator and AuthNavigator is a stack navigator
// Before the app crashing all the correct state is set but it just crashes
// ----------- Final Stack Navigator ----------------
const FinalStackNavigator = createStackNavigator();
export const FinalNavigator = (props: any) => {
const authContext = useContext(AuthContext);
const [signedIn, setSignedIn] = useState<boolean | null>(
authContext.isLoggedIn
);
useEffect(() => {
if (authContext.isLoggedIn !== signedIn && !authContext.isLoading) {
setSignedIn(authContext.isLoggedIn);
}
}, [authContext]);
return (
<FinalStackNavigator.Navigator>
{signedIn ? (
<>
<FinalStackNavigator.Screen
name="BottomNavigator"
component={BottomNavigator}
options={{ headerShown: false }}
/>
</>
) : (
<>
<FinalStackNavigator.Screen
name="AuthNavigator"
component={AuthNavigator}
/>
</>
)}
</FinalStackNavigator.Navigator>
);
};
AppNavigator.tsx
export const AppNavigator = (props: any) => {
return (
<NavigationContainer>
<FinalNavigator />
</NavigationContainer>
);
};
-------- Login Logic flow -------------
on success of login API I call this line
await authContext.changeCofficToken(result.data.login.token);
AuthContext.ts
const changeCofficToken = async (token: string) => {
await setItem("cofficToken", token);
setIsLoggedIn(true);
setCofficToken(token);
};
helper.ts
export const setItem = async (key: string, value: any) => {
await AsyncStorage.setItem(key, value);
};
App.tsx
return (
<AuthContextProvider>
<FilterContextProvider>
<SearchTermContextProvider>
<ShowVerticalListContextProvider>
<CouponContextProvider>
<ApolloProvider client={client}>
<AppNavigator />
</ApolloProvider>
</CouponContextProvider>
</ShowVerticalListContextProvider>
</SearchTermContextProvider>
</FilterContextProvider>
</AuthContextProvider>
);
------ End of Login Logic code flow -------
Error logs from my IOS Simulator
com.apple.CoreSimulator.SimDevice.957FA264-501C-44C3-80F3-9E8D1F600A1E[15500] (UIKitApplication:host.exp.Exponent[e036][rb-legacy][15601]): Service exited due to SIGABRT
assertion failed: 19F101 17F61: libxpc.dylib + 83746 [ED46009E-B942-37CC-95D4-82CF3FE90BD8]: 0x7d
Please Note :- I am using Expo if it matters
Video Link :- https://fisico-dhaval.s3.ap-south-1.amazonaws.com/Screen+Recording+2020-08-20+at+5.38.06+PM.mov
If you need any other code snippets then please do lemme know
Any help will be appreciated
Can you share the code where you setSignin. In case if you are saving and clearing token, make sure those libraries are supported and linked properly.
Service exited due to SIGABRT is a common issue in Native iOS development, it can be cause due to various events. While working on react-native, I have never experienced this error. But I'm adding some of my observation, may be that can help you,
This can be cause due to some buggy component, which are not compatible with Expo version. So, you need to add many console.log or may be comment some things and check is there any functionality or a component causing this error. A buggy component can be a culprit.
If you have pod installed, Make sure you check all your libraries.You likely have some issue with an installed external library. and make sure you run pod install and delete node_modules folder and then run npm install again (close the packager too)
You can also use sentry or any other crash reporting library to detect crashes, It will give you the exact line number.
Sometimes the crash reporting libraries won't work, the reason can be that the crash occurs before the js bundle is loaded and because of that, the library is not yet initialised and it can't detect the crash. If so you can install a native crash reporting library, You can use fabric for that, or even bugsnag.
These are few scenarios, I've observed via which you can debug or detect what's the real cause of this error.
I'm trying to conditionally render my redux app based on if the user is logged in. The relevant & condensed version of my code is below:
let isLoggedIn = false;
export default function App() {
console.log('App executing...');
console.log('isLoggedIn: ', isLoggedIn);
return (
<Provider store={store}>
<NavigationContainer>
{isLoggedIn ? ContactsTab() : Login()}
</NavigationContainer>
</Provider>
);
}
store.subscribe(() => {
// Set isLoggedIn to true if token is received and reinvoke App()
if (store.getState().user.token) {
isLoggedIn = true;
App();
}
});
The app starts with console logging isLoggedIn: false and displaying Login()(as expected). When I login on my phone using the correct credentials, App() is re-invoked console logging isLoggedIn: true(as expected) but it's still displaying Login(). If I set isLoggedIn = true inside the app function, the app successfully starts displaying the ContactsTab().
What is happening here? Why is my app not moving to ContactsTab() when the value of isLoggedIn successfully changes to true? How can I fix this?
Thank you for reading along. I have been trying to debug this for the past 2 days with no success so any help would be greatly appreciated!
You need to use useState here like this, the useState will automatically renders when the state changes
export default function App() {
const [isLoggedIn, setLoggedIn] = useState(false);
console.log('App Executing...');
console.log('isLoggedIn: ', isLoggedIn);
store.subscribe(() => {
// Set isLoggedIn to true if token is received and reinvoke App()
if (store.getState().user.token) {
setLoggedIn(true);
}
});
return (
<Provider store={store}>
<NavigationContainer>
{isLoggedIn ? ContactsTab() : Login()}
</NavigationContainer>
</Provider>
);
}
Hope this helps!
What is happening here? Why is my app not moving to ContactsTab() when the value of isLoggedIn successfully changes to true? How can I fix this?
Re-invoking app doesn't necessarily re-renders your screen. Your conditional invoking is not working properly because your render method is only called once, to fix it you need to change the state of your App component. You are just changing the state through your reducer but you are not listening to that change in your app component. You must listen to that change and upon that change, you need to set login state to true and then your component will perform rendering for you.
Read more about state here.
Read more about how you can use redux to make your components listen to the change in state of your application here.
In RN my bilingual app (English - Arabic), I have used I18nManager (views) and I18n (for translations)
When I am changing app language to Arabic, the whole app gets reloaded again from the splash-screen using this code:
I18nManager.forceRTL(true)
Ideally, it should not restart the app from start and it should continue with the current screen with Arabic data.
Currently, it is not happening, only translation elements are getting converted using I18n.t('keyword') but for views Arabic alignment, it's not proper.
Still looking for a better solution, let me know if anyone achieved it.
Thanks
Sopo !!
you should put this code in the top component in your project
import RNRestart from "react-native-restart";
I18nManager.forceRTL(true);
if (!I18nManager.isRTL) RNRestart.Restart();
If you guys wants to store stack state after reloading(because there is no other option without reloading) and want stack state back you can follow this link also you can check my code.
Link: React navigation state persist
Any Component
AsyncStorage.setItem('navigation_state', JSON.stringify(navigation.dangerouslyGetState()));
My App.js
const App = () => {
const [initialState, setInitialState] = useState();
const [isReady, setIsReady] = useState(false);
useEffect(() => {
restoreState();
}, []);
const restoreState = async () => {
try {
const savedStateString = await AsyncStorage.getItem('navigation_state');
const state = savedStateString ? JSON.parse(savedStateString) : undefined;
if (state !== undefined) {
AsyncStorage.removeItem('navigation_state');
setInitialState(state);
}
} finally {
setIsReady(true);
}
};
if (!isReady) {
return null;
}
return (
<Provider store={store}>
<NavigationContainer
initialState={initialState}
ref={rootNavigationRef}>
<Root>
<AppNavigator />
</Root>
</NavigationContainer>
</Provider>
);
};
I working on a project which has two languages, Arabic and English.i use redux for handling app language. I put all styles on redux and handle app style with redux. and when user change language all styles on my app change to that language . also all text handled with redux too. with this way, my app does not reload and app language changed immediately.
If your app is an android hybrid app, you can try this:
import com.facebook.react.modules.i18nmanager.I18nUtil;
I18nUtil i18nUtil = I18nUtil.getInstance();
i18nUtil.forceRTL(context, forceRtl);
i18nUtil.allowRTL(context, true);
value 'forceRtl' is a boolean.
for iOS,I think you can find the same method.
In Expo use
import {Updates} from "expo"
Updates.reload()
I'm writing a simple app with React Native and Expo.
This app has ~10 small to medium sized images that are used in different places within the app.
From what I read, unless I cache these images, they will be required to be downloaded from expo each time.
For this reason, I have noticed that they seem to load in really slowly when testing the app. Upon building and navigating through the app, I find that it takes a few seconds for my images to pop up even after the rest of the page has loaded.
I followed the setup as seen in the starting template.
Here is what my App.js looks like (I am using react-navigation so it varies from the sample file above):
export default class App extends React.Component {
state = {
isLoadingComplete: false
};
componentDidMount() {
StatusBar.setHidden(true);
}
render() {
_loadResourcesAsync = async () => {
return Promise.all([
Asset.loadAsync([
require("./assets/syria.png"),
require("./assets/lebanon.png"),
require("./assets/kenya.png"),
require("./assets/indonesia.png"),
require("./assets/somalia.png"),
require("./assets/india.png"),
require("./assets/america.png"),
require("./assets/albania.png"),
require("./assets/bosnia.png")
])
]);
};
_handleLoadingError = error => {
Alert.alert(error);
};
_handleFinishLoading = () => {
this.setState({ isLoadingComplete: true });
};
if (this.state.isLoadingComplete == false) {
return (
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
/>
);
} else {
return (
<AppContainer
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
);
}
}
}
I have excluded my react-navigation code for the sake of brevity.
When I run this, my app gets stuck at Downloading JavaScript bundle 100.00%.
It seems that the _handleFinishLoading never runs. At least, that's the only reason I can see for it to never finish loading.
Given the small amount of images, I don't know how this could take more than a second. Instead it sits at the splash screen forever.
Any ideas on what I might be doing wrong here?
Found the solution:
I made a simple error. The async functions (_loadResourcesAsync, _handleFinishLoading, etc) need to be outside the render method. Moving them below my render method inside of the app class caused this to work as expected.