Expo v4 / React : Passing datas between tab screen - react-native

Expo v4..
Hello friends,
I am using expo tab navigation:
https://reactnavigation.org/docs/hello-react-navigation
How can I pass datas bewteen expo tabs ?
OrderScreen.tsx:
import { createMaterialTopTabNavigator } from '#react-navigation/material-top-tabs';
const Tab = createMaterialTopTabNavigator();
export default function OrderScreen({navigation, route}) {
let data = route.params
return (
<Tab.Navigator>
<Tab.Screen name="Details" component={DetailsScreen} />
<Tab.Screen name="Photos" component={PhotosScreen} />
</Tab.Navigator>
);
}
PhotosScreen.tsx:
export default function PhotosScreen() {
// HOW TO GET route.params here ??
DetailScreen.tsx:
export default function DetailsScreen() {
// HOW TO GET route.params here ??
I manage to have data in only one tab using :
navigation.navigate('Order', {screen: 'Details' ,params: {datas:someDatas}});
But I want to have the datas in both tabs.
I have been searching everywhere but impossible to find.
I would be very surprise there is no solution for that.
T H A N K S !!!! :-)

finding no answer on the net, I've been using the context api.
for a guy new to react it took a while to get the idea of context but it seems to be quite powerful once settled.

Related

React Navigation how to setup children routes

I have a React Native Expo application using React Navigation. I think I have all the navigation routes (links) not well setted up.
So in my app let's say I have a Profile screen which has 2 list items to navigate. Application settings & User settings. These two have some more items inside each one.
My navigation tree would be something like:
ProfileScreen (screen with 2 items => User settings | Application settings)
UserSettings:
Language (Navigation to LanguageScreen)
Change password (Navigation to ChangePasswordScreen)
Personal data (Navigation to PersonalDataScreen)
ApplicationSettings:
...
My LinkingConfiguration is configured like:
profileStack = {
initialRouteName: "profile",
screens: {
profile: "profile",
userSettings: "profile/userSettings",
language: "profile/userSettings/language",
changePassword: "profile/userSettings/password",
personalData: "profile/userSettings/personalData",
applicationSettings: "profile/applicationSettings"
}
}
profileStack is a navigation stack. The rest are screens.
I'm wondering how I need to setup well the tree, because if I deep link to myApp.com/profile/userSettings/language, if I go back, I go directly to the initialRouteName which is profile, and I guess this is because the tree is not well generated.
In angular router would be setted as
profile: path: "...", children: [ userSettings: path: "...", children: [ {...}]]
How does navigation tree have to be setted up correctly in my example? Maybe using many navigation stacks?
Can anybody enlight me?
FYI:
React Navigation: v6 (it was also happenning in v5)
React Native: v0.69.5
Expo: v46.0.9
React Navigation allows you to set up the same hierarchy you showed in your example. You want to nest the navigators just as in your depiction.
https://reactnavigation.org/docs/nesting-navigators
Something like:
function Profile() {
return (
<Stack.Navigator>
<Stack.Screen name="UserSettings" component={UserSettings} />
<Stack.Screen name="ApplicationSettings" component={ApplicationSettings} />
</Stack.Navigator>
);
}
function UserSettings() {
return (
<Stack.Navigator>
<Stack.Screen name="Language" component={Language} />
<Stack.Screen name="ChangePassword" component={ChangePassword} />
<Stack.Screen name="PersonalData" component={PersonalData} />
</Stack.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Profile />
</NavigationContainer>
);
}
The structure of navigation is created by how you nest components.

React Native: How to handle protected routes with react navigation

What I want is to protect specific screens where the user can run specific actions and only at that point redirect him to a login screen, if the user is successfully authenticated, redirect him to the point where he left off the flow.
We can do this in React JS(with react-router), so I would like to know if its posible to implement a similar solution for react-native(with react-navigation):
React JS approach with React Router
function RequireAuth({ children }) {
const { authed } = useAuth();
const location = useLocation();
return authed === true ? (
children
) : (
<Navigate to="/login" replace state={{ path: location.pathname }} />
);
}
This is possible in react native and is documented in the authentication workflow section of the documentation.
In summary we conditionally need to render screens inside the navigator. Here is a minimal example using a Stack.Navigator which is similar to your posted code snippet. The workflow is identical for other navigators (nested or not). I will use the context api in my example, but there are other ways to achieve the same as well. The overall pattern is the same.
const AppContext = React.createContext()
function App() {
const [isSignedIn, setIsSignedIn] = React.useState(false)
const appContextValue = useMemo(
() => ({
isSignedIn,
setIsSignedIn,
}),
[isSignedIn]
)
return (
<AppContext.Provider value={appContextValue}>
<Stack.Navigator>
{!isSignedIn ? (
<Stack.Screen
name="Login"
component={LoginScreen}
/>
) : (
// whatever screens if user is logged in
<Stack.Screen name="Home" component={HomeScreen} />
)}
</Stack.Navigator>
</AppContext.Provider>
);
In the LoginScreen, you then need to change the authed state to true if the login was successful. The first screen in the conditional will then be initially rendered automatically (in this case HomeScreen).
function LoginScreen() {
const { setIsSignedIn } = useContext(AppContext)
// do whatever, then setIsSignedIn to true
}

Cannot create custom StackNavigator - "Couldn't register the navigator"?

I'm trying to create a custom StackNavigator and distribute it on a custom package. The problem is that when I want to apply my custom Stack I get the following error:
Error: Couldn't register the navigator. Have you wrapped your app with
'NavigationContainer'?
This is my App.tsx:
import { createThemedStack } from '#my-custom-package'
//this is the reference to my custom navigator
const ThemedStack = createThemedStack()
function App() {
return (
<NavigationContainer>
<ThemedStack.Navigator>
<ThemedStack.Screen name='Screen 1' component={Screen1} />
<ThemedStack.Screen name='Screen 2' component={Screen2} />
...
</ThemedStack.Navigator>
</NavigationContainer>
)
}
export default App
ThemedStack.tsx - Here I want to apply some custom common styles to the stacks:
import * as React from 'react'
import { useNavigationBuilder, createNavigatorFactory, StackRouter } from '#react-navigation/native'
import { StackView } from '#react-navigation/stack'
// #ts-ignore
function ThemedStack({ initialRouteName, children, screenOptions, ...rest }) {
const { state, descriptors, navigation } = useNavigationBuilder(StackRouter, {
initialRouteName,
children,
screenOptions,
})
return <StackView {...rest} state={state} navigation={navigation} descriptors={descriptors} />
}
export default createNavigatorFactory(ThemedStack)
What am I doing wrong? I dont get it. The ThemedStack navigator is inside a NavigationContainer.
I did this based on https://reactnavigation.org/docs/custom-navigators/ RN Navigation docs, although they don't say anything about doing it in external package structure.
Thanks in advance :)
Sounds like you have #react-navigation/native in dependencies in your package which results in multiple versions installed. You should move it to peerDependencies.

Testing react-native-testing-library with react-navigation v5

I'm looking to test react-navigation v5 with react-native-testing-library. The documentation says to do the following.
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { render, fireEvent } from '#testing-library/react-native';
describe('Testing react navigation', () => {
test('page contains the header and 10 items', async () => {
const component = (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);
const { findByText, findAllByText } = render(component);
const header = await findByText('List of numbers from 1 to 20');
const items = await findAllByText(/Item number/);
expect(header).toBeTruthy();
expect(items.length).toBe(10);
});
});
https://callstack.github.io/react-native-testing-library/docs/react-navigation
https://medium.com/#dariaruckaolszaska/testing-your-react-navigation-5-hooks-b8b8f745e5b6
This medium article suggests I create a MockedNavigator component. would this be a mockedNavigator component for all of my screens? Is this reusable?
import React from 'react';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
const Stack = createStackNavigator();
const MockedNavigator = ({component, params = {}}) => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="MockedScreen"
component={component}
initialParams={params}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
export default MockedNavigator;
The answer I'm looking for is. Am I to use the real AppNavigator component within all my unit tests or am I to use a MockedAppNavigator?
Furthermore, how am I to pass props?
The documentation is unclear and I am looking for clarity. Many of the new components we are working with use hooks and the react-navigation-v5 is unable to access certain props.
There are two recommended approaches for RNTL tests involving React Navigation.
Either you test the same navigators you use in your app. This does not have to be the whole AppNavigator if your tests contain nested navigators. It can be only the one you want to test.
Or you test single screen not connected to any navigator. This would probably require refactor of that screen into the React Navigation-aware XxxScreen that would not be tested and XxxScreenContent that would receive relevant props from XxxScreen but would be indenpendend of XxxScreen.
You can find more details in the official React Navigation example in RNTL repo.
The mocked navigator approach is possible but not really advised as it's mocking too much, as such tests frequently have to rely on implementation details not visible to the users like route names, navigator nesting, etc.

React Native Error: The action 'NAVIGATE' with payload {"name":"GameScreen"} was not handled by any navigator

I know that this issue has been brought up before but I have not found any answer that works for me.
I simply want to navigate from screen "Start" to screen "Game", using App.js as the router.
App.js:
const Stack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Start">
<Stack.Screen name="Start" component={StartingScreen} />
<Stack.Screen name="Game" component={GameScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
StartingScreen.js:
const StartingScreen = ({ navigation }) => {
//lots of code
return (
//more code
<Button title="Begin" onPress={() => {navigation.navigate('GameScreen')}}/>
)
This gives me the error in the title with ("Do you have a game named 'GameScreen'?") and nothing happens. I have tried following the React Navigation docs, but in their example they put everything in App.js, that does not work for me. Other things I have tried include exporting the navigation stack to StartingScreen.js, changing the arguments of navigation.navigate(), placing the navigator inside StartingScreen.js.
GameScreen is spelled exactly the same in all places.
Change your code like below
const StartingScreen = ({ navigation }) => {
//lots of code
return (
//more code
<Button title="Begin" onPress={() => {navigation.navigate('Game')}}/>
)
You are giving the component name but you should provide the name of the screen that you give in the stack which is 'Game'
As your screen name is given 'Game' in the stack navigator, you should navigate to 'Game' instead of 'GameScreen'.
onPress={() => {navigation.navigate('Game')}}