React-navigation 5x usage of Stack.Screen screenProps - react-native

I want to pass something by screenProps in React-navigation v5.x.x. I am one of the newcomers in react-native. Can anyone help me?

There's no screenProps in React Navigation 5. You can use React's Context feature instead to pass data down the tree without an extra API.
https://reactnavigation.org/docs/upgrading-from-4.x#global-props-with-screenprops

in my case I am passing my data like :
props.navigation.navigate('toScreen', {
resumeDetail: data,
})
and you can access it like :
detail = this.props.navigation.state.params.resumeDetail;

https://reactnavigation.org/docs/screen/#children
Render callback to return React Element to use for the screen:
<Stack.Screen name="Profile">
{(props) => <ProfileScreen {...props} />}
</Stack.Screen>
You can use this approach instead of the component prop if you need to pass additional props. Though we recommend using React context for passing data instead.
Note: By default, React Navigation applies optimizations to screen components to prevent unnecessary renders. Using a render callback removes those optimizations. So if you use a render callback, you'll need to ensure that you use React.memo or React.PureComponent for your screen components to avoid performance issues.
This is what I use at the moment:
// ...
export const ScanPage = React.memo(ScanComponent);
function useScreenWithProps(
PageComponent: React.FC<Props>,
props: Props
) {
// Take note of the double arrow,
// the value useMemo returns is a function that returns a component.
return useMemo(
() => (navigationProps: NavigationProps) => (
<PageComponent {...navigationProps} {...props} />
),
[PageComponent, props]
);
}
const Stack = createStackNavigator();
const Navigator: React.FC<Props> = (props) => {
const scan = useScreenWithProps(ScanPage, props);
const activate = useScreenWithProps(ActivatePage, props);
const calibrate = useScreenWithProps(CalibratePage, props);
const success = useScreenWithProps(SuccessPage, props);
const error = useScreenWithProps(ErrorPage, props);
return (
<Stack.Navigator>
<Stack.Screen name="Scan">{scan}</Stack.Screen>
<Stack.Screen name="Activate">{activate}</Stack.Screen>
<Stack.Screen name="Calibrate">{calibrate}</Stack.Screen>
<Stack.Screen name="Success">{success}</Stack.Screen>
<Stack.Screen name="Error">{error}</Stack.Screen>
</Stack.Navigator>
);
};

Related

react-native passing props AND navigation

In a react-native child-component, I need to read both parent-props and navigation.
I use this code in parent to pass DIC-props to child, which works just fine:
...
<Stack.Screen name="SignIn>
{(props) => <SignIn {...props} DIC={DIC} />}
</Stack.Screen>
...
In Child comp. I get that prop (DIC) like this, so far all fine:
const SignIn = (props) => {
const { DIC } = props
...
}
But in Child I need now to get navigation from props too, but this does not work (navigation appears as an empty object)
const SignIn = (props, {navigation}) => {
const { DIC } = props
...
Can someone see what am I doing wrong? How can I get both specific props AND navigation? Thx!
I really recommend you using Typescript so that you can better understand what is happening under the hood. Anyway, as for your question, this should work for you:
const SignIn = ({ navigation, route }) => {
const DIC = route.params.DIC
...
}
you can get navigation like this:
const { DIC, navigation } = props;
navigation comes within props.

Dynamically select the initial route name in react-navigate

My app will have 4 main pages: A, B, C and Homepage
Before showing the Homepage the user will need to go through A, B and C (sequentially, using react-navigation) to collect some information. I will store the information in the device using expo-secure-store.
If the user completed all the steps, the next time the app will start I want to show directly the Homepage screen.
Considering that SecureStore.getItemAsync is async, I'm not able to query it when I'm starting my app:
const Stack = createStackNavigator();
export default function App() {
const data = await SecureStore.getItemAsync("pincode");
var firstScreen = data ? "Homepage" : "A";
/* alt solution - not working
SecureStore.getItemAsync("data").then((data) => {
firstScreen = data ? "Homepage" : "A";
});
*/
return (
<NavigationContainer>
<Stack.Navigator initialRouteName={firstScreen}>
<Stack.Screen name="A" component={AScreen} />
<Stack.Screen name="B" component={BScreen} />
<Stack.Screen name="C" component={CScreen} />
<Stack.Screen name="Homepage" component={HomepageScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
If I use await I get Unexpected reserved word 'await', while if I do it in an async way, the firstScreen value will be of course set to late.
You want to put any State (so your firstScreen into react State with useState.
You want (for your 'problem') to initalise the state when the component is mounted. We useEffect with an open dep's array to handle that. As the state is held in SecureStore which we retrieve with an Async call I've written a function that will use Async/Await to retrieve this and populate the state when it is done.
As suggested above, you might want to show some form of Loading process so that users are not shown a page that is not ready yet!
Something like (BTW - you may want to lift this into a navigation component to keep your App component simple!):
const Stack = createStackNavigator();
export default function App() {
const [firstScreen, setFirstScreen] = useState('');
const [loading, setLoading] = useState(true);
useEffect(() => {
SecureStore.getItemAsync("pincode").then(
(data) => {
setFirstScreen(data? 'Homepage' : 'A');
setLoading(false);
}
).catch((err) => {
setLoading(false);
setFirstScreen('A');
});
}, []);
if (loading) {
return null; //Or something to show that you are still warming up!
}
return (
<NavigationContainer>
<Stack.Navigator initialRouteName={firstScreen}>
<Stack.Screen name="A" component={AScreen} />
<Stack.Screen name="B" component={BScreen} />
<Stack.Screen name="C" component={CScreen} />
<Stack.Screen name="Homepage" component={HomepageScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}

React Navigation 5: Switching between different stack navigators in React native

I'm finding difficulty in digesting the official documentation and I'm stuck finding out a solution to move between different stack navigators. I have provided my current implementation and code snippet to explain my problem better.
I have a bottom tab navigator and 2 stack navigators to handle different use cases.
BottomNavigation.js
StackNavigation.js
AuthStackNavigation.js
I have created multiple stack navigators within StackNavigation.js and rendering each StackNavigators within BottomNavigation.js
***StackNavigation.js***
const Stack = createStackNavigator();
const HomeStackNavigator = () => {
return (
<Stack.Navigator initialRouteName="Home" screenOptions={ScreenLogo}>
<Stack.Screen name="HomeScreen" component={Home} />
</Stack.Navigator>
);
}
const ProfileStackNavigator = () => {
return (
<Stack.Navigator initialRouteName="Home" screenOptions={ScreenLogo}>
<Stack.Screen name="MahaExpo" component={Profile} />
</Stack.Navigator>
);
}
export { HomeStackNavigator, ProfileStackNavigator };
And as I said I'm rendering each navigator inside tab navigator to switch between screens.
***BottomNavigation.js***
import { HomeStackNavigator, ProfileStackNavigator } from '../Navigations/StackNavigation'
const Tab = createMaterialBottomTabNavigator();
function BottomNavigation() {
return (
<Tab.Navigator
initialRouteName="Home" >
<Tab.Screen
name="Home"
component={HomeStackNavigator}
/>
<Tab.Screen
name="ProfileStackNavigator"
component={ProfileStackNavigator}
/>
</Tab.Navigator>
)
}
export default BottomNavigation
and I'm rendering this within app.js and inside NavigationContainer. I have created AuthStackNavigation.js which has a login and register screens.
***AuthStackNavigation.js***
const AuthStack = createStackNavigator();
const AuthStackNavigation = ({ }) => (
<AuthStack.Navigator headerMode='none'>
<AuthStack.Screen name="UserLogin" component={UserLogin}></AuthStack.Screen>
<AuthStack.Screen name="UserRegister" component={UserRegister}></AuthStack.Screen>
</AuthStack.Navigator>
);
export default AuthStackNavigation
So currently, I'm showing the home screen with some public content and user can switch to different screens using the bottom tab navigator linked to different screens. When the user clicks on profile tab, im displaying a button to login which should take the user to AuthStackNavigation.js which has a login and register screen. Thanks in advance!
const ProfileStackNavigator = () => {
return (
<Stack.Navigator initialRouteName="Home" screenOptions={ScreenLogo}>
<Stack.Screen name="MahaExpo" component={Profile} />
<Stack.Screen name="UserLogin" component={UserLogin} />
<Stack.Screen name="UserRegister" component={UserRegister} />
</Stack.Navigator>
);
}
on profile page check user is logged in or not, if not, simply navigate to UserLogin screen. Once login simply pop back to profile page are refresh.
There's no way of communicating between two separate navigators if they're not related to each other (one is a child navigator of another or etc.), unless you create something like RootStackNavigator, which would be on the higher level and which would contain all the navigators you have listed above.
Create guest navigators array, authorized navigators array and some shared routes (if needed). Guest navigator contains authstack only and authorized one contains other navigators, if you use redux, context or something like this you can check for the token on startup to determine which navigator you have to use. Logging in will give you the token and will automatically change your navigator to authorized navigators and logging out will throw you back (as you no longer have the token) to the guest navigators which will contain only authentication flow.
Pretty hard to explain ;) hope this helps..
Redux Example
`
const authNavigators = [
{
type: "screen",
key: "authNavigators",
name: "authNavigators",
component: authNavigators,
},
];
const otherNavigators = [
{
type: "screen",
key: "otherNavigators1",
name: "otherNavigator1",
component: otherNavigators1,
},
{
type: "screen",
key: "otherNavigators2",
name: "otherNavigator2",
component: otherNavigators3,
},
...
];
const RootStack = () => {
const { condition } = useSelector(
(state) => state.store);
let navigators = otherNavigators;
if(condition) {
navigators = authNavigators;
}
return (
<RootStackNavigator.Navigator>
{navigators.map((item) => {
return (
<RootStackNavigator.Screen
{...item}
options={({ route: { params } }) => ({
...item.options,
})}
/>
);
})}
</RootStackNavigator.Navigator>
);
};
export default RootStack;`
from redux, you can dispatch an action which would change that condition and this would dynamically update your navigator.

Navigation Error when navigating to Home page from Firebase registration

I'm following this guide: https://www.freecodecamp.org/news/react-native-firebase-tutorial/ in attempt to learn how to use firebase, and even though I've followed the code very closely, I'm receiving a NAVIGATION error:
The action 'NAVIGATE' with payload {"name":"Home","params":{"user":{"id":"AWSKEmmUsua5koR1V3x5bapc3Eq2","email":"tk#gmail.com","fullName":"t"}}} was not handled by any navigator.
Do you have a screen named 'Home'?
I do however, have a screen named Home. App.js:
import Home from './src/Home';
import Login from './src/Login/Login';
import Registration from './src/Registration/Registration';
const Stack = createStackNavigator();
export default function App() {
const [loading, setLoading] = useState(true);
const [user, setUser] = useState(null);
return (
<NavigationContainer>
<Stack.Navigator>
{ user ? (
<Stack.Screen name="Home">
{props => <Home {...props} extraData={user} />}
</Stack.Screen>
) : (
<>
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Registration" component={Registration} />
</>
)}
</Stack.Navigator>
</NavigationContainer>
);
}
When I use the Registration form to register a new user and Navigate to the Home page is when I get the error. Registration.js:
import { firebase } from '../firebase/config';
export default function Registration({ navigation }) {
const [fullName, setFullName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const onFooterLinkPress = () => {
navigation.navigate('Login');
}
const onRegisterPress = () => {
if (password !== confirmPassword) {
alert("Passwords do not match!");
return
}
// This works. However, navigation does not for some reason
firebase.auth()
.createUserWithEmailAndPassword(email, password)
.then((response) => {
const uid = response.user.uid
const data = {
id: uid,
email,
fullName
}
const usersRef = firebase.firestore().collection("users");
usersRef.doc(uid).set(data).then(() => {
// This is where the navigation error lies. It has nothing to do with the component
// This error happened even when I created a new plain Home component
navigation.navigate("Home", { user: data})
})
.catch((error) => alert(error))
})
.catch((error) => alert(error))
}
return (
....Input Forms
<TouchableOpacity
style={styles.loginButton}
onPress={() => onRegisterPress()}
>
<Text style={styles.buttonTitle}>Create Account</Text>
</TouchableOpacity>
I have used React Navigation before and haven't run into this issue. I am not using nested navigators and cannot see where the issue lies. Thank you for reading.
Adding to Göksel Pırnal answers:
At first, suppose there is no user. So We are in Registration Screen. At that stage, our navigator doesn’t even know whether there is any “Home” Screen. At this stage, our navigator only knows 2 screens: “Login” and “Registration” screens.
You need to notify our app.js whether anyone registered in the Registration screen or not. After that our app.js should change the value of 'user' in [user,setUser].
In your, App.js put this lines of code:
const [initializing,setInitializing]=useState(true)
useEffect(()=>{
const subscriber=firebase.auth().onAuthStateChanged((user)=>{
setUser(user)
setInitializing(false)
})
return subscriber
},[])
if (initializing) return null //Here you may use an Activity indicator
Then after rerendering our navigator will see the value of “user” has changed and it should navigate to the Home screen.
And guess what! You do not need to navigate manually from Registration Screen as you already put a condition in App.js ( in return () ).
You have a problem where you check the user value in App.js. After the registration is done, you did not assign the state in the App.js page and it will always be null. The Home page will not be added to the stack because the user value is null. That's why you got the error.
Solution: You need to notify App.js after registration.

onStateChange doesn't get called when wrapped in React.Component

I am trying to wrap the NavigationContainer in React component for screen tracking, this approach worked well in V4 but fails in V5 unfortunately. The follow-up question is: will it be possible to wrap it in a function component and not in the react component to have the ability to use hooks? (must admit I am relatively new to react)
Will really appreciate any assistance
App.js
const Drawer = createDrawerNavigator();
function MyDrawer() {
return (
<Drawer.Navigator drawerType="front" drawerPosition="left">
<Drawer.Screen name="Properties" component={PropertiesTabs} />
<Drawer.Screen name="Profile" component={Profile} />
</Drawer.Navigator>
);
}
const AppContainer = function App() {
return <NavigationContainer>{MyDrawer()}</NavigationContainer>;
}
export default with(AppContainer);
Wrapper.tsx
export function with(Component: any) {
class PNDContainer extends React.Component {
debounce;
componentDidMount() {
console.log('PND Mounted - First Time Screen');
}
componentWillUnmount() { }
render() {
return (<Component onStateChange={() => {
console.log('Screen Changed Doesnt get Called !!!');
}} />);
}
}
return PNDContainer;
}
Expected Behavior
onStateChange should be called, in the V4 the same approach did trigger the onNavigationStateChange
Enviroment
#react-navigation/native 5.7.0
react-native 0.61.5
node 13.10.1
yarn 1.22.1
I can understand why it doesnt work, as I am passing a function element that has no such prop onStateChange, in V4 CreatAppContainer returned a component that had the prop onNavigationStateChange
So I would like to call the function get the element and "inject" my onStateChange implementation, but I think react doesnt work that way (its more like imperative way and react is a declarative framework) so what will be a better approach?
So I tried to debug in chrome to see the onStateChange, no luck...
I think that I misunderstand the concepts, I read the following React Function Components
Edit
For now the only solution that worked for me is to wrap my component in NavigationContainer and returned it
<NavigationContainer onStateChange={() => {console.log('ProbablynewScreen');}}>{Component()}
</NavigationContainer>);
In that case, I noticed that discovering drawer is not that simple there is no clue in the state for the drawer and if I use Class component (unfortunately currently I must) I have no hooks, so how would I discover a drawer open/close ?
App.js
const Drawer = createDrawerNavigator();
function MyDrawer(props) {
return (
<NavigationContainer onStateChange={props.onStateChange}>
<Drawer.Navigator drawerType="front" drawerPosition="left">
<Drawer.Screen name="Properties" component={PropertiesTabs} />
<Drawer.Screen name="Profile" component={Profile} />
</Drawer.Navigator>
</NavigationContainer>
)
};
export default with(MyDrawer)
With.tsx
export function with(Component: any) {
class PNDContainer extends React.Component {
child: any;
componentDidMount() {
//debugger;
console.log('PND Mounted - First Time Screen');
}
componentWillUnmount() { }
render() {
debugger;
return (<Component onStateChange={(state) => {
debugger;
console.log('Screen Changed');
}} />)
}
}
return PNDContainer;
}
Thanks to WhiteBear