So, I finished my app using React Router and everything works as expected. When the user enters the page, he gets redirected to a loading screen while my App component fetches a DB, and after the fetch is done the user gets redirected to the category that was fetched.
/ -> <Loader /> -> /App().state.data[0].category.
The fetch is done so fast that the user cannot even see the loading screen, as expected. So the user experiences.
/ -> /App().state.data[0].category.
So my problem is that when the user is on the /App().state.data[0].category page, and he hits the back button, he will see and stay on the loading screen. So he basically has to double click to go back to the previous page. This is not desired.
What the user experiences
/App().state.data[0].category -> <Loader /> -> Page before entering the "/"
What the user should experience
/App().state.data[0].category -> Page before entering the "/"
My Question is there a way to jump the loading screen if the user presses the back button?
I started looking into the history npm thing, am I on the right track?
My current config looks like this in pseudocode:
main.js
render(
<Router history={hashHistory}>
<Route path="/" component={App}>
/* This returns the first category from my api */
<IndexRedirect to={App().state.data[0].category} />
<Route path=":category" component={App} />
</Route>
</Router>
,document.getElementById('main'));
App.js
class App extends React.Component{
componentWillMount() {
const router = this.context.router;
fetchData().then(function(results){
router.push(results.data[0].category);
})
}
render() {
return (<Loader/>);
}
}
why not do a conditional render on a single component.
if database -> render the page
else -> render the loader
as soon as your db fetch is complete, the render will mount and you will see your page instead of your loader
Related
I'm having a difficult time trying to figure out how to log out both the user on the expo app as well as logging them out from the react-native-webview.
The user can log out of the expo app by pressing logout (which just handles the auth logic), however; the user is still logged in inside the webview (which is the shop uri).
At the moment, I came up with two possible ways: either set incognito to true on the webview, or send the user to a Screen which returns another webview to the shopify's logout uri.
// sending user to shopify logout webview
//navigator
<Stack.Navigator>
<Stack.Screen
name="AccountDetails"
component={AccountDetailsScreen}
/>
<Stack.Screen
name="ShopWebview"
component={ShopWebview}
/>
<Stack.Screen
name="LogOut"
component={LogOutWebView}
/>
</Stack.Navigator>
// from `AccountDetails` component, user presses handleLogout()
const handleLogOut = () => navigation.navigate('LogOut');
// shopify logout webview
export const LogOutWebView = () => {
return (
<WebView
source={{ uri: 'https://www.quarantotto.co.jp/account/logout' }}
/>
);
};
This seems to work w/ android, but it doesn't seem to work on iOS? I'm not quite sure why that is.
I can still set the incognito to true likeso:
// ShopWebview
<WebView
incognito
source={{ uri: 'someshop.com/' }}
/>
and this would help me not store the logged in user's session, but not quite sure if this is the proper way to handle auth.
If I can use the first method of sending the user to a webview that handles logout when a user visits it, that would be great. But not sure why it's not working from ios.
any ideas?
UPDATE: possible working solution?
Whenever the user logs out, they are sent back to a screen w/ login or register.
The issue was that whenever the user pressed login, since their session was cached, they are redirect to their account screen (which is what I don't want since if a user presses logout, I want them to be logged out and have to log back in).
what I have done is just inject some js in the LoginWebview
const handleNavigationStateChange = (newNavState) => {
if (webViewRef.current) {
webViewRef.current.injectJavaScript(scriptFetchCustomer());
}
if (newNavState.url.includes('logout')) {
setTimeout(() => {
const redirectTo = `window.location = "${someshop.com/login}"`;
webViewRef.current.injectJavaScript(redirectTo);
}, 500);
}
};
seems to work, but I don't enjoy using the settimeout. any recommendations?
second update... doesn't work on android
Android isn't working properly. I've set the source to the shopify's /account/logout uri, but inside handleNavigationStateChange the newNavState's url isn't the logout as it's set on the source.
....
Last update:
It finally works as I wanted to. I had to check for the url changes inside onNavigationStateChange of the webview. Then I could do what I wanted. I basically sent user to /account/logout, then set current logged in user state back into null which fires a redirect to login screen.
Hope it helps someone.
I have an app that starts with a loader screen where I determine whether the user is an admin or not. This screen navigates to a BottomTabNavigator, but I want to show different tabs based on whether the user is admin or not. I looked at the documentation for custom navigators but this still requires the navigator to be created outside the class, so I can't use the params. I also tried this:
export default class BottomTabNavigator extends Component {
render() {
const BottomTabComponent = createBottomTabNavigator({
...
});
return (
<BottomTabComponent {...this.props} />
);
}
}
But that doesn't work either (seems like I shouldn't pass the navigation prop down). Removing {...this.props} is also no good because then I would have to wrap the component in an app container, but that is also not right, so I'm not sure how to proceed.
I am using React-Native, React-Navigation and Redux. I have a sign up page which will send the user information to the server and will listen for a token. Once the token is received, i will change the global state {userLoggedIn: true}.
In my component, i use a componentDidUpdate to check whether userLoggedIn is true, and if it is, then i will redirect to the "Main" page.
// component/register
class Register extends Component {
...
componentDidUpdate(){
console.log('componentDidUpdate from the register page')
if (this.props.people.userLoggedIn==true){
this.props.navigation.navigate('Main');
}
}
render(){
//sign up form
}
}
...
export default connect(mapStateToProps, mapDispatchToProps)(Register);
This works fine as I am redirected to the Main page. However, on the Main page itself, it is refreshing non-stop. I checked the log and saw that componentDidUpdate is still being called from the register page.
Therefore I am a little confuse why is componentDidUpdate still being called? Wouldn't it stop checking after it is being navigated to a new page?
EDIT 1: I realised it is not a problem with componentDidUpdate. I inserted the redirect condition insider the render method, it also causes an infinite loop. I can't understand why it is still trying to render when it is navigated to a new page.
It looks like your SignUp page keeps getting new props and updating after you are navigated to the Main page. I think this is due to navigation prop that is updated. In your componentDidUpdate you should make sure you are going to change page only in one case - when your userLoggedIn is changed from false to true.
// component/register
class Register extends Component {
...
componentDidUpdate(prevProps){
console.log('componentDidUpdate from the register page')
if (this.props.people.userLoggedIn === true && prevProps.people.userLoggedIn === false) {
this.props.navigation.navigate('Main');
}
}
render(){
//sign up form
}
}
You can use withNavigationFocus to know if the component is focused.
I already checked similar existing issues on stack-overflow or gihub, like:
NavigationCard will only re-render when the route changes, Help: renderScene is executed just once and does not refresh updated props, etc. But my case is different.
Page list:
sign in page
home page: user can only see this page after sign in;
Transition logic:
In "sign in page", after sign in, it shall go to "home page"
In "home page", there is a sign out button, after user click, it shall go back to "sign in" page.
My implementation
I created a top level component called App, and the code looks like the below:
// app.js
class App extends Component {
componentWillMount() {
const {doNavigateReplaceAtIndex} = this.props;
let {isSignedIn} = this.props;
doNavigateReplaceAtIndex(isSignedIn? 'home': 'sign-in');
}
render() {
const {globalNavigationState} = this.props;
return (
<NavigationCardStack
navigationState={globalNavigationState}
style={styles.container}
onNavigate={()=>{}}
renderScene={this._renderScene}
/>
);
}
_renderScene({scene}) {
const { route } = scene;
switch(route.key) {
case 'home':
return <Home />
case 'sign-in':
return <SignIn />
}
}
}
export default connect (
state =>({
isSignedIn: !! state.auth.token,
token: state.auth.token,
globalNavigationState: state.globalNavigation
}),
dispatch => ({
doNavigateReplaceAtIndex: (key) => dispatch(navigateReplaceAtIndex(0, key))
})
)(App);
// sign in page
// after signin, it will doNavigateReplaceAtIndex(0, 'home');
// home page
// after clicking "sign out", it will doNavigateReplaceAtIndex(0, 'sign-in');
// doNavigateReplaceAtIndex basically is just call NavigationStateUtils.replaceAtIndex
Symptoms
At beginning, it shows sign in page, after signing in, it goes to home page, it is good. In home page, when click the sign out button, it didn't move anywhere, but when refresh, it shows sign in page.
What I got so far
It is not because of this issue: NavigationCard will only re-render
when the route
changes,
because I debugged into rn source code, the shouldComponentUpdate didn't block;
I am not sure if I didn't right for doNavigateReplaceAtIndex, usually we use push or pop, but my case I cannot use push/pop, because after sign in, we should not allow use to go back sign in page by clicking "BACK" button on Android.
I think the issue may because of NavigationScenesReducer(which is called by NavigationAnimatedView used in NavigationCardStack), it will mark all previous routes as stale, and will not show it them.
Any help will be welcome, thanks.
My environment
react native: 0.29.1
react native cli: 1.0.0
node: 5.6.0
OS: ios 9.3
Using React-Native (0.19) and Redux, I'm able to navigate from scene to scene in React Components like so:
this.props.navigator.push({
title: "Registrations",
component: RegistrationContainer
});
Additionally I'd like to be able push components to the navigator from anywhere in the app (reducers and/or actions).
Example Flow:
User fills out form and presses Submit
We dispatch the form data to an action
The action sets state that it has started to send data across the wire
The action fetches the data
When complete, action dispatches that the submission has ended
Action navigates to the new data recently created
Problems I'm seeing with my approach:
The navigator is in the props, not the state. In the reducer, I do not have access to the props
I need to pass navigator into any action that needs it.
I feel like I'm missing something slightly simple on how to access Navigator from actions without sending in as a parameter.
In my opinion the best way to handle the navigation with Redux is with react-native-router-flux, because it can delegate all the navigation logic to Redux:
You can change the route from the reducer;
You can connect the router to the store and dispatch its own actions that will inform the store about route changes (BEFORE_ROUTE, AFTER_ROUTE, AFTER_POP, BEFORE_POP, AFTER_DISMISS, BEFORE_DISMISS);
An example
Here is an example on how easily you can save the currently focused route in the store and handle it in a component (that will be aware of being focused):
1. Connect a <Route> to Redux
Connecting a <Route> to Redux is easy, instead of:
<Route name="register" component={RegisterScreen} title="Register" />
you might write:
<Route name="register" component={connect(selectFnForRegister)(RegisterScreen)} title="Register" />
You can also simply connect the component itself in its own file like you usually do.
2. Connect a <Router> to Redux
If you need to inform Redux of the navigation status (i.e. when you pop a route) just override the <Router> component included in react-native-router-flux with a connected one:
import ReactNativeRouter, { Actions, Router } from 'react-native-router-flux'
const Router = connect()(ReactNativeRouter.Router)
Now when you use a <Router> it will be connected to the store and will trigger the following actions:
Actions.BEFORE_ROUTE
Actions.AFTER_ROUTE
Actions.AFTER_POP
Actions.BEFORE_POP
Actions.AFTER_DISMISS
Actions.BEFORE_DISMISS
Take a look at this for an example.
3. Catch the interested actions in your reducer
For this example I have a global reducer (where I keep the information needed by all my app) where I set the currentRoute:
case Actions.AFTER_ROUTE:
case Actions.AFTER_POP:
return state.set('currentRoute', action.name)
Now the reducer will catch every route change and update global.currentRoute with the currently focused route.
You also can do many other interesting things from here, like saving an history of the navigation itself in an array!
4. Update your component on focus
I'm doing it on componentDidUpdate of my component of the route named payment.
If global.currentRoute is payment and the previous global.currentRoute was different, than the component has just been focused.
componentDidUpdate(prevProps) {
const prevRoute = prevProps.global.currentRoute
const currentRoute = this.props.global.currentRoute
if (currentRoute === 'payment' && prevRoute !== currentRoute) {
this.props.actions.doSomething()
}
}
P.S.: Remember to check currentRoute === 'payment', otherwise you'll start doSomething() on every route change!
Also, take a look a the README.md for learning other stuff about the integration with Redux.
Hope it helps, long live Redux!
Maybe you could pass the information about title and component in an action and the component with the navigator can then push the right scene with the information of the state.