react native navigationExperimental NavigationCardStack renderScene didn't re-render for previous routes - react-native

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

Related

how to get last navigation route when app is reloaded in react native?

I have a react-native app
and I define one splash screen. when user is Authenticated, it navigate to home and when user is not Authenticated, it navigate to login
its work correctly, but:
I want to when the app is in develop mode and when reloaded, it navigate to last route
can you help me to fix this ux problem?
You can access to the state of navigation, source here https://reactnavigation.org/docs/use-navigation-state/
import { useNavigationState } from '#react-navigation/native';
const usePreviousRouteName =() => {
return useNavigationState(state =>
state.routes[state.index - 1]?.name
? state.routes[state.index - 1].name
: 'None'
);
}

How to go back to initial Screen for drawer when opening deeplink with backBehaviour as history?

I'm using a Drawer Navigator as my base with history and have a Home screen and a Notifications screen.
Now for a deeplink myapp://notifications i want to have a history like Home->Notifications so that on pressing physical back btn the user is taken to Home instead of closing the app.
However it seems this does not work with backBehaviour set as history as now Home is never added when opening deeplink.
Is there any way to update the history so that it always has Home as initial screen?
(I have set Home as Initial screen in my linking config but that does not help)
The reset action allows resetting the navigation state to the given state.
Try to run this dispatch if your app opened using the deep link
(Untested) example:
import { CommonActions } from '#react-navigation/native';
navigation.dispatch((state) => {
// Add the home route to the start of the stack
const routes = [{ name: 'Home' }, ...state.routes];
return CommonActions.reset({
...state,
routes,
index: routes.length - 1,
});
});
Read more about reset action

React Navigation: How to fake a history in Stack Navigator?

I use https://reactnavigation.org/ and my app has a register process consisting of several screens:
First, the user has to enter his first name ("FirstNameView")
Second, the user has to enter his last name ("LastNameView")
...
If the user closes the app in the LastNameView and opens it later again, the app sends a request to the backend to fetch the current user data. Depending on this data, the user is navigated to the right screen via the initialRouteName property:
<MainStack.Navigator
initialRouteName="LastNameView"
>...
I have a back button on every screen which does navigation.goBack(), which obviously not works if the user just opens the app again and I direct him to the right View without pushing other screens to it.
One approach that I could think of is removing the navigation.goBack() functionality and replacing it with navigation.navigate("nameOfPreviousView"). However, this has the drawback that native "back" functionality of e.g. Android would not work.
How can I handle a case like this? Can I somehow create a fake history with my initialRouteName approach?
Yes you can create previous route history without navigating there.
In NavigationContainer initialize initialState like
let initialState = initialRoute = {
index: 1, // to load the second screen which is LastNameView
routes: [
{
name: 'FirstNameView',
},
{
name: 'LastNameView',
},
],
};
Like
<NavigationContainer initialState={initialState}> {/* ... */} </NavigationContainer>
To explore more about initialState check description here https://reactnavigation.org/docs/navigation-container/#initialstate

How to pass data from one component to Navigator and from Navigator to component

I'm actually doing a login system and when my user is logged, the login component has to send data to my main screen but I don't really understand how to do it.
Actually I've this when user is logged :
User.username = data.username;
User.id = responseJson.id;
User.token = responseJson.token;
this.props.navigation.navigate('Main', { User: User });
User is where everything is saved and it works.
Data is sended to Main a Switch Navigates inside an AppContainer :
export default createAppContainer(createSwitchNavigator(
{
Auth: { screen: AuthStack, navigationOptions: { tabBarVisible: false } },
Main: { screen: MainStack },
},
{
initialRouteName: "Auth"
}),
);
so it goes on Mainstack who's a bottomTabNavigator and works like that :
const MainStack = createBottomTabNavigator(
{
Services: {
screen: Home,
navigationOptions: {
tabBarLabel:"Services",
tabBarIcon: ({ tintColor }) => (
<Icon name="home" size={30} color="#0033CC" />
)
},
},
I know it's not complete I've other screens not only Services, just wanted to avoid too long function on paste.
So is that possible to send this data "User" to my Home screen ?
I'm open to any suggestion or answer, that's my first react Native project so there is maybe some mistakes :)
You'll probably want to persist the user data in the app (save it in the storage for when the user leaves the app then enters he stays logged in). That's why react-navigation has the switchNavigator (the one you are using). In order to persist data, you can use the AsyncStorage to save the user data and in the switch navigator constructor check whether the user data is already available in the storage or not then decide whether to navigate to the app or the authentication page. In fact React Navigation provides an example on this: https://reactnavigation.org/docs/en/auth-flow.html . Since you're new to RN, here's an extra tip on managing data: you can use a State in every component that is only for that specific component (which is also reactive), you can use global data (store) shared between all the components in the app (check react redux/flux/mobx), you can share data from parent components to children components by passing data/functions in the props (aka attributes), and you can use the new React context api to use a provider (also like a store shared between parent and children components)
I've made something maybe a bit more understandable :
I get my user data in component login from an external api and it works well. To go from login to home I'm using this :
this.props.navigation.navigate('Main', { User: User})
So it goes on Main who opens Mainstack Screen who's a bottomTabNavigator and if I don't do any mistakes my User data that I've sent is on this Mainstack but I don't know how to get it and how to share it to the other components.
Every component got his own js file, there is only the App container and Authstack/Mainstack that are on the same file.
Hope you understood it !

How to pass parameters to a deep react-navigation Stack Navigator

I have a react-native app using react-navigation. I want to pass some props all along the stack navigator, but not sure what is the right thing to do.
To give more details, my screens are:
ListThingsScreen: List all things.
ShowThingScreen: Show details of the thing.
EditThingScreen: Edit the thing.
They are organized in such way:
const HomeStack = createStackNavigator(
{
ListThings: ListThingsScreen,
ShowThing: ShowThingScreen,
EditThing: EditThingScreen,
},
{
initialRouteName: "ListThings"
}
);
export default createBottomTabNavigator({
HomeStack
});
An app user:
Log in.
See all things (in ListThingsScreen).
Click on one of them, jump to ShowThingScreen to see its details.
Click on a button of the details screen, jump to EditThingScreen to edit the thing.
Once finished editing, jump back to ShowThingScreen.
In ListThingsScreen componentDidMount(), I will make async call to fetch logged in user data such as userId, username, etc. I want all screens in the stack knows about them to behave correctly when show and when edit.
Solutions I can think of:
Pass parameters to routes, as suggested by react-navigation docs. This works, but as I have more screens in the stack, I write too much code like navigation.push("RouteName", {user}) and navigation.getParam("user", "No user").
This is pretty much the only solution I have found. An alternative is to use React context. I like this approach better, however I want to use react-navigation and the concept of Router and Route don't exist in the library.
// ListItemsScreen.js
import { BrowserRouter as Router, Route } from "react-router-dom";
const UserContext = React.createContext();
<UserContext.Provider value={{ user }}>
<Router>
<div className="app-container">
<Route path="/show/:thingId" component={ShowThing} />
<Route path="/edit/:thingId" component={EditThing} />
</div>
</Router>
</UserContext.Provider>
// ShowItemScreen.js
<UserContext.Consumer>
{({user}) => (...)}
</UserContext.Consumer>
Which approach is more "correct"? If it is the 2nd approach, how can I do it with react-navigation?