How to set-up structure for react-navigation v6? - react-native

I am trying to upgrade a large app from v4 to v6 and I'm having issues even getting the first screen to render. I get the error: Couldn't find a navigation object. Is your component inside a screen in a navigator?. I feel like it's something wrong with the set-up but I can't quite figure it out. I followed through the upgrading docs from v4 to v5 to start out, but the examples do not have the same structure as the app I am working with.
Here's App.js:
import navigationRef from '../Navigation/NavigationService';
export const App = () => {
return (
<Provider store={store}>
<NavigationContainer ref={navigationRef}>
<RootContainer />
</NavigationContainer>
</Provider>
);
};
Here's NavigationService.ts:
export const topLevelNavigation = React.createRef<NavigationContainerRef>();
Here's RootContainer.tsx:
class RootContainer extends Component {
backHandlerSubscription: NativeEventSubscription;
componentDidMount = async (): Promise<void> => {
this.backHandlerSubscription = BackHandler.addEventListener('hardwareBackPress', this.onAndroidBackPress);
};
componentWillUnmount = async (): Promise<void> => {
this.backHandlerSubscription.remove();
};
onAndroidBackPress = (): null => {
NavigationService.navigateBack();
return null;
};
render(): ReactNode {
return (
<>
<View style={Styles.rootContainer}>
<AppNavigator /> //error occurs here!!
<AppStateManager />
</View>
</>
);
}
}
const mapDispatchToProps = () => ({});
export default connect(null, mapDispatchToProps)(RootContainer);
Here's the stack navigator:
const RootStack = createStackNavigator();
const RootContainerStackNavigator = () => {
return (
<RootStack.Navigator initialRouteName="StartupSplashScreen" screenOptions={{ gestureEnabled: false }}>
<RootStack.Screen name="StartupSplashScreen" component={StartupSplashScreen} />
<RootStack.Screen name="LoginScreen" component={LoginScreen} />
</RootStack.Navigator>
);
};
Here is the AppNavigator class:
type AppNavigatorProps = {
navigation?: any;
route?: RouteProp<any, any>;
};
class AppNavigator extends Component<AppNavigatorProps> {
// non-navigation related stuff here
render(): ReactNode {
const { navigation } = this.props;
return (
<>
<SafeAreaView style={topSafeAreaStyle} />
<SafeAreaView style={bottomSafeAreaStyle}>
<StatusBar backgroundColor={barColor} barStyle={barStyle}
<ErrorBoundary
navigation={navigation}
<RootContainerStackNavigator />
</ErrorBoundary>
</SafeAreaView>
</>
);
}
}
export default function (props) {
const navigation = useNavigation();
return <AppNavigator {...props} navigation={navigation} />;
}

Related

React Native provider contexts and handling multiple contexts

So, I want to consume a context TaskContext in just two components. There is a way to do this, without wrapping my whole application with the TaskContext ? Furthemore, what would be the best way to handle multiples context ?
I've try wrapping a single component with the context by creating another function that returns a new component. For example:
const HomePageContext = (props) => {
return (
<TaskProvider>
<HomePage {...props}/>
</TaskProvider>
)
}
But with this approach, the data update in AddTask screen doesn't appear in HomePage.
(Disclaimer: I just want to create a homePage that contains a list of tasks. This tasks are created in another screen, AddTasks. When the task is created, the homepage should be update to display the new list, with all other tasks and the new one. Thus, i dont know what is the best approach to do this.)
My code is here. It's only for test and practice purpose.
import React, { createContext, useReducer, useContext} from 'react'
import { View, FlatList, Text, Button } from 'react-native'
import { createNativeStackNavigator } from '#react-navigation/native-stack'
import { createDrawerNavigator } from '#react-navigation/drawer'
import { NavigationContainer } from '#react-navigation/native'
const TaskContext = createContext({})
const initialState = [{ name: `Task name ${Math.floor(Math.random() * 100)}`, id: Math.floor(Math.random() * 100) }]
//contexto provider
const TaskProvider = ({ children }) => {
const reducer = (state, action) => {
switch(action.type) {
case 'addTask':
console.log([...state, action.payload])
return [...state, action.payload]
default:
return state
}
}
const [tasks, dispatch] = useReducer(reducer, initialState)
return (
<TaskContext.Provider value={ { tasks, dispatch } }>
{ children }
</TaskContext.Provider>
)
}
const HomePage = ({ navigation }) => {
const { tasks, dispatch } = useContext(TaskContext)
return (
<View>
<Text>Home</Text>
<FlatList
data={tasks}
keyExtractor={item => `${item.id}`}
renderItem={({ item }) => <Text>{item.id} - {item.name}</Text>}
/>
<Button
title='Add Task'
onPress={() => navigation.navigate('AddTask')}
/>
</View>
)
}
const Info = () => {
return (
<View>
<Text>Info</Text>
</View>
)
}
const AddTaskPage = () => {
const { dispatch } = useContext(TaskContext)
const newTask = {id: Math.floor(Math.random() * 100), name: `Task name ${Math.floor(Math.random() * 100)}`}
return (
<View>
<Text>addTaskPage</Text>
<Button
title='AddTask'
onPress={() => dispatch({
type: 'addTask',
payload: newTask
})}
/>
</View>
)
}
// createNavigators
const Stack = createNativeStackNavigator()
const Drawer = createDrawerNavigator()
const DrawerNavigation = () =>{
return (
<Drawer.Navigator>
<Drawer.Screen name='Home' component={HomePage}/>
<Drawer.Screen name='Info' component={Info}/>
</Drawer.Navigator>
)
}
// App component
export default App = () => {
return (
<TaskProvider>
<NavigationContainer>
<Stack.Navigator initialRouteName='Menu'>
<Stack.Screen name='Menu' options={{ headerShown: false }} component={DrawerNavigation} />
<Stack.Screen name='AddTask' component={AddTaskPage} />
</Stack.Navigator>
</NavigationContainer>
</TaskProvider>
)
}

Getting navigation.state outside of a screen in react-navigation v5/v6

I'm trying to upgrade a react-native v4 app to v5/v6. In v4, createAppContainer was used like so:
const AppContainer = createAppContainer(AppNavigator);
and AppNavigator would receive a Navigation prop that could be used like:
type AppNavigatorProps = {
navigation: Navigation;
};
class AppNavigator extends Component<AppNavigatorProps> {
isSplashScreen = (): boolean => {
const { navigation } = this.props;
return navigation.state.index === RouteIndexes.SplashScreen;
};
render(): ReactNode {
// styling set-up here
return (
<>
<SafeAreaView style={topStyle} />
<SafeAreaView style={bottomStyle}>
<StatusBar backgroundColor={barColor} />
<ErrorBoundary
navigation={navigation}>
<RootContainerStackNavigator navigation={navigation} />
</ErrorBoundary>
</SafeAreaView>
</>
);
}
}
I cannot figure out a way to get that navigation.state in this location anymore. I have the AppNavigator wrapped in NavigationContainer instead of the createAppContainer like so:
<NavigationContainer>
<AppNavigator />
</NavigationContainer>

How to pass the selected value of dropdown to an independent componenet using redux and reactnative?

this my first component contain a dropdown
********** SelectDropDown.js*************
export default function SelectDropDown() {
const [val, setVal] = useState(null);
data=["option1","option2","option3","option4",]
const handleSelect = (e) => {
console.log(data[e]);
setVal(e);
};
return (
<View style={styles.container}>
<ModalDropdown
options={data}
multipleSelect={false}
defaultValue="Selectionner OF"
style={styles.DropDown}
onSelect={handleSelect}
/>
</View>
);
}
i want to pass the selected value of the dropdown to Screen1 (ButtonsGroupe,Detaille) and screen2
Screen1.js**********
export default function Screen1() {
return (
<>
<SelectDropDown />
<ButtonsGroupe />
<Detaille />
</>
);
}
Screen2.js**********
export default function Screen2() {
return (
<>
<ButtonsProd />
<Production />
<DataProd />
<Details />
</>
);
}
i'am puted Screen1 and Screen 2 in StackNavigator to navigate between them
Nav.js*******
export default function Nav() {
const Stack = createStackNavigator();
return (
<>
<Stack.Navigator initialRouteName="Screen1" screenOptions={{headerShown: false}}>
<Stack.Screen name="Screen1" component={Screen1}/>
<Stack.Screen name="Screen2" component={Screen2}/>
</Stack.Navigator>
</>
);
}
and finally i'am called those component to app.js
**app.js
export default function App() {
return (
<>
<NavigationContainer>
<StatusBar animated={true} hidden={true} />
<HeaderBarNavigation />
<Nav />
</NavigationContainer>
</>
);
}
please can someone help me to resolve this task
My understanding of what you want to do is that you are trying to pass some data between screens that are not relative (neither child or parent).
Since you are using react-navigation, you are allowed to pass params when you are calling the screen.
I would suggest somthing like that :
export default function SelectDropDown({navigation}) {
const [val, setVal] = useState(null);
data=["option1","option2","option3","option4",]
const handleSelect = (e) => {
console.log(data[e]);
setVal(e);
};
useEffect(() => {
if(val){
navigation.navigate('Screen1', {superCoolParam : val})
}
}, [val]);
return (
<View style={styles.container}>
<ModalDropdown
options={data}
multipleSelect={false}
defaultValue="Selectionner OF"
style={styles.DropDown}
onSelect={handleSelect}
/>
</View>
);
}
And then :
export default function Screen1({route}) {
useEffect(() => {
console.log('Catching params => ', route.params.superCoolParams)
}, [route]);
return (
<>
<SelectOF />
<ButtonsGroupe />
<Detaille />
</>
);
}

Pass navigation from child component to parent getting TypeError

I'm facing an issue whenever i tried to navigate to another screens. I'm using the navigation in child component and it doesn't work even i passed the props to the parent component. This is my first time on using react navigation. I've tried many possible solution yet still can't solve this issue. I'm using react navigation 5 and i need help. I'm getting an error as such :
TypeError: undefined is not an object (evaluating 'this.props.navigation.navigate')
Home.js // Parent Component
class Home extends Component {
render() {
return (
<Cards
title="In Progress"
imgUri={require('../assets/CardProgress.png')}
navigateAction={() =>
this.props.navigation.navigate('SiteAudit')
}
)
}
}
Card.js // Child Component
const Cards = (props) => {
return (
<CardContainer
backgroundColor={props.backgroundColor}
onPress={() => {
props.navigation.navigateAction;
}}>
<CardWrapper>
<CardTitle>{props.title}</CardTitle>
<CardImage source={props.imgUri} />
</CardWrapper>
</CardContainer>
);
};
import React from 'react';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import Dashboard from './screens/Dashboard';
import SiteAudit from './screens/SiteAudit';
const RootStack = createStackNavigator();
const DashboardStack = createStackNavigator();
const SiteAuditStack = createStackNavigator();
const DashboardScreen = () => {
return (
<DashboardStack.Navigator>
<DashboardStack.Screen name="Dashboard" component={Dashboard} />
</DashboardStack.Navigator>
);
};
const SiteAuditScreen = () => {
return (
<SiteAuditStack.Navigator>
<SiteAuditStack.Screen name="SiteAudit" component={SiteAudit} />
</SiteAuditStack.Navigator>
);
};
const Navigation = () => {
return (
<NavigationContainer>
<RootStack.Navigator initialRouteName="Dashboard">
<RootStack.Screen name="Dashboard" component={DashboardScreen} />
<RootStack.Screen name="SiteAudit" component={SiteAuditScreen} />
</RootStack.Navigator>
</NavigationContainer>
);
};
export default Navigation;
Edit your card view as follows
<Cards
title="In Progress"
imgUri={require('../assets/CardProgress.png')}
onPress={() => this.buttonPress()}
/>
const buttonPress = () => {
this.props.navigation.navigate('Site Audit');
};
Edit your button as follows,
<TouchableOpacity
style={styles.cardButton}
onPress={onPress}
<Text style={styles.cardTitle}>{this.props.title}</Text>
<Image style={styles.imageCard} source={this.props.imgUri} />
</TouchableOpacity>
Solved the issue, I just need to re-read the documentation of react navigation. I need to use the useNavigation that has been stated here https://reactnavigation.org/docs/use-navigation/
Cards.js // Child Component
const Cards = (props) => {
return (
<CardContainer
backgroundColor={props.backgroundColor}
onPress={props.navigationAction}>
<CardWrapper>
<CardTitle>{props.title}</CardTitle>
<CardImage source={props.imgUri} />
</CardWrapper>
</CardContainer>
);
};
Home.js // Parent Component
import {useNavigation} from '#react-navigation/native';
const Home = () => {
const navigation = useNavigation();
return (
<Cards
title="Completed"
backgroundColor="#0082C8"
navigationAction={() => {
navigation.navigate('Site Audit');
}}
/>
)
}

Why after login my react-navigation routing does not work properly?

I have navigation container(created in react-navigation)
const AppStack = createStackNavigator();
const AppStackScreen = () => (
<AppStack.Navigator>
<AppStack.Screen name="Tabbed" component={TabsScreenNavigationScreen} />
</AppStack.Navigator>
);
class AppNavigationContainer extends Component {
constructor(props) {
super(props);
this.state = {
appLoading : true,
}
}
user = {};
componentDidMount() {
let _this = this;
this.getUser()
.then(() => {
this.setState({appLoading: !_this.state.appLoading})
})
}
getUser = async () => {
return await AsyncStoreService.getUserFromStore()
.then((user) => {
this.user = user;
});
}
render() {
const user = this.user;
const {
appLoading
} = this.state;
return (
<NavigationContainer>
{appLoading ?
<SplashScreen/>
:
<>
{user ?
<AppStackScreen/>
:
<AuthStackNavigationScreen/>
}
</>
}
</NavigationContainer>
)
}
}
export default AppNavigationContainer;
How can you see I have separated modules for app and login. login router:
const AuthStack = createStackNavigator();
const AuthStackNavigationScreen = () => (
<AuthStack.Navigator>
<AuthStack.Screen
name="ChooseRole"
component={SelectRoleScreen}
options={{title: false}}
/>
<AuthStack.Screen
name="AuthStart"
component={MainScreen}
options={{title: false}}
/>
<AuthStack.Screen
name="SignIn"
component={LoginScreen }
options={{title: 'Sign In'}}
/>
<AuthStack.Screen
name="CreateAccount"
component={RegisterScreen}
options={{title: 'Create Account'}}
/>
</AuthStack.Navigator>
);
export default AuthStackNavigationScreen;
Tabbed router for app:
const GalleryStack = createStackNavigator();
const SearchStack = createStackNavigator();
const MessagesStack = createStackNavigator();
const MenuStack = createStackNavigator();
const Tabs = createBottomTabNavigator();
const GalleryStackScreen = () => (
<GalleryStack.Navigator>
<GalleryStack.Screen name="Gallery" component={GalleryScreen} />
<GalleryStack.Screen name="GalleryItem" component={GalleryItemScreen} />
</GalleryStack.Navigator>
);
const SearchStackScreen = () => (
<SearchStack.Navigator>
<SearchStack.Screen name="Search" component={SearchScreen} />
<SearchStack.Screen name="SearchResults" component={SearchResultsScreen} />
</SearchStack.Navigator>
);
const MessagesStackScreen = () => (
<MessagesStack.Navigator>
<MessagesStack.Screen name="ConversationList" component={ConversationListScreen} />
<MessagesStack.Screen name="Conversation" component={ConversationScreen} />
</MessagesStack.Navigator>
);
const MenuStackScreen = () => (
<MenuStack.Navigator>
<MenuStack.Screen name="Menu" component={MenuScreen} />
<MenuStack.Screen name="About" component={AboutScreen} />
</MenuStack.Navigator>
);
const TabsScreenNavigationScreen = () => (
<Tabs.Navigator>
<Tabs.Screen name="Gallery" component={GalleryStackScreen} />
<Tabs.Screen name="Search" component={SearchStackScreen} />
<Tabs.Screen name="Messages" component={MessagesStackScreen} />
<Tabs.Screen name="Menu" component={MenuStackScreen} />
</Tabs.Navigator>
);
export default TabsScreenNavigationScreen;
So on login screen name="SignIn" I login, perform navigation.navigate('Tabbed') after succesfully login and get message:
The action 'NAVIGATE' with payload {"name":"Tabbed"} was not handled by any navigator.
Do you have a screen named 'Tabbed'?
He doesnt 'see' my tab navigation. Why it happens so(I have such screen name and put it to render), and how can I fix this?
According to the stack you have you will either have the appstack or the authstack at a given moment
<>
{user ?
<AppStackScreen/>
:
<AuthStackNavigationScreen/>
}
</>
So you cant navigate to tabbed from signin screen which does not exist.
The way you can handle this is update the user object maybe using a callback function or use context instead of state which will trigger a render of the AppNavigationContainer and the tabbed stack will automatically rendered instead of the auth stack. You wont need a navigate you should do the same for logout to where you will set the user to null.
You can refer more on Auth flows