I'm have a problem in react navigation, error message "undefined is not an object (evaluating 'navigation.navigate')".
Error happens when trying to call a screen by a button from another route
Component code:
export default function Estruturas({ title, display, navigation }) {
const [estruturas, setEstruturas] = useState([]);
useEffect(() => {
async function loadEstruturas() {
const response = await api.get('/produto/api/estrutura');
setEstruturas(response.data);
}
loadEstruturas();
}, []);
return (
...
<ProductList>
{estruturas.map(estrutura => (
<Item key={estrutura.id} onPress={()=> navigation.navigate('Pedidos')}>
...
Routes code:
const Tab = createMaterialBottomTabNavigator();
function MyTabs(){
return(
<Tab.Navigator
barStyle={{ backgroundColor: '#694fad' }}
initialRouteName='Feed'
activeColor='black'
>
<Tab.Screen
name="Início"
component={Dashboard}
options={{
tabBarLabel: 'Início',
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons name="home" size={26} />
),
}}
/>
<Tab.Screen
name="Pedidos"
component={Requests}
options={{
tabBarLabel: 'Pedidos',
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons name="assignment" size={26} />
),
}}
/>
</Tab.Navigator>)}
export default function Routes() {
return (
<NavigationContainer>
<MyTabs />
</NavigationContainer>
);
}
I tried with Stack but it gave the same error
Only the direct child of your navigator stack will have access to the navigation prop. I can see that your component Estruturas is not a direct route so it will not get the navigation prop directly inside the function props. There are 2 ways to get this done and fixed.
Either use a withNavigation HOC provided by react-navigation v4 or if you are using v5 then use useNavigation hook.
OR you need to pass the navigation prop to the component wherever you are calling this component. This can be done as below:-
export default function Dashboard(props) {
return (
<View>
<Estruturas navigation={props.navigation} />
</View>
);
}
Related
I have a FlatList with some swipeable components inside of it, using Swibepeable from react-native-gesture-handler. My current navigation structure goes as follows:
I have a routes folder with a bottom tabs navigator file and a stack navigator file. The stack navigator is nested inside of the tabs navigator, which looks like this:
TabsNavigator:
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
import HomeStack from "./HomeStack";
export default function TabsNavigator() {
const Tab = createBottomTabNavigator();
return (
<Tab.Navigator screenOptions={{ headerShown: false }}>
<Tab.Screen
name="List"
component={HomeStack}
options={{
tabBarIcon: ({ focused }) => (
<FontAwesomeIcon
icon={faList}
style={{ color: focused ? "#104543" : "#CCC" }}
/>
),
}}
/>
</Tab.Navigator>
);
}
HomeStack:
import { createStackNavigator } from "#react-navigation/stack";
import { Home } from "../screens/Home";
export default function StackNavigator() {
const Stack = createStackNavigator();
return (
<Stack.Navigator>
<Stack.Screen
options={{ headerShown: false }}
name="Home"
component={Home}
/>
</Stack.Navigator>
);
}
However, since the app changed the StackNavigator (HomeStack) has become irrelevant. Thus I've tried to insert the {Home} screen into the Tab.Screen's component, thinking this would do the trick. It did do the trick, except that I can't swipe any of the rows in the FlatList anymore. I'm still able to open the Accordion ListItem inside of the FlatList however. For extra clarity I'll add the Home screen as well.
Home:
export const Home = ({ navigation }) => {
const dispatch = useDispatch();
useEffect(() => {
const fetchUserData = async () => {
const data = await axios.get(
`http://192.168.****`,
{
auth: {
username: "****",
password: "***",
},
}
);
dispatch(setUsersData(data.data));
};
return navigation.addListener("focus", async () => {
await fetchUserData();
});
}, [navigation]);
return (
<View style={styles.container}>
<SearchBarHeader/>
<FlatListComponent />
</View>
);
};
I haven't seen any similar issue online yet. Does anyone have an idea what could cause this?
Here is my Context File:
import React from 'react';
import { createContext, useState } from 'react';
export const TabContext = createContext({
opened: false,
toggleOpened: () => {},
});
export const TabContextProvider = ({ children }) => {
const [opened, setOpened] = useState(false);
const toggleOpened = () => {
setOpened(!opened);
};
return (
<TabContext.Provider value={{ opened, toggleOpened }}>
{children}
</TabContext.Provider>
);
};
My Simplified App.js File: (Necessary files are imported)
const Tab = createBottomTabNavigator();
export default function App() {
const buttonCtx = useContext(TabContext);
return (
<>
<TabContextProvider>
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen
name='Action'
component={ActionScreen}
options={{
tabBarButton: () => (
<ActionButton
opened={buttonCtx.opened}
toggleOpened={buttonCtx.toggleOpened}
/>
),
}}
/>
</Tab.Navigator>
</NavigationContainer>
</TabContextProvider>
</>
);
}
And the Simplified ActionButton Component:
export default function ActionButton({ opened, toggleOpened }) {
return (
<View style={styles.container}>
<View style={styles.box}>
<TouchableWithoutFeedback
onPress={toggleOpened}
style={styles.actionButton}
>
/* With an Animated View inside */
</TouchableWithoutFeedback>
</View>
</View>
);
}
Basically, **toggleOpened **should switch the value of the variable **opened **between true and false. So the **AnimatedView **can work properly which solely depends on the value of opened.
Opened is readable in all of the components, no problem with that. But **toggleOpened **is not working at all. Any idea?
In order to use contexts properly, you need to have at least two components working together,one that renders the Provider and one descendant who then uses that context.
You are trying to provide and use the context at the same time,try to move the consumer component one position down to the hierarchy.
For example in your App.js you can create a consumer component to wrap your ActionButton,then pass the context to it as you did :
export default function App() {
return (
<>
<TabContextProvider>
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen
name="Action"
component={ActionScreen}
options={{
tabBarButton: () => <ActionButtonWrapper />
}}
/>
</Tab.Navigator>
</NavigationContainer>
</TabContextProvider>
</>
);
}
const ActionButtonWrapper = () => {
const { opened, toggleOpened } = useContext(TabContext);
return (
<>
<ActionButton
opened={opened}
toggleOpened={toggleOpened}
/>
</>
);
};
However,i would just use the context directly within ActionButton,after all,passing props down to children is what we want to avoid by using context,right?
I have created for you a snippet to see how we can properly use context
I want to achieve a native stack navigator for each tab. However I want different Stack navigator files for each tab. So I created different files but i'm getting a warning in the console:
Found screens with the same name nested inside one another. Check:
Home, Home > Home
This can cause confusing behavior during navigation. Consider using
unique names for each screen instead. at
node_modules/#react-navigation/core/src/BaseNavigationContainer.tsx:364:14 in React.useEffect$argument_0
Also the the Home screen is nested inside Home screen.I tried changing the name of the Home from Home to Homescreen component still the issue persists.
Here are the functions in my files:
Home.js
function Home({ navigation }) {
return (
<View style={styles.screen}>
<Text>Home screen!</Text>
<Button title="Details" onPress={() => navigation.navigate("Details")} />
</View>
);
}
Details.js
const Details = (props) => {
return (
<View>
<Text>Details will appear here!</Text>
</View>
);
};
HomeStackScreen.js
import { createNativeStackNavigator } from "#react-navigation/native-stack";
import HomeScreen from "../screens/Home";
import DetailsScreen from "../components/Details";
const HomeStack = createNativeStackNavigator();
function HomeStackScreen() {
return (
<HomeStack.Navigator>
<HomeStack.Screen name="Home" component={HomeScreen} />
<HomeStack.Screen name="Details" component={DetailsScreen} />
</HomeStack.Navigator>
);
}
AppNavigator.js
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
import { NavigationContainer } from "#react-navigation/native";
import HomeStackScreen from "./HomeStackScreen";
const Tab = createBottomTabNavigator();
function AppNavigator() {
return (
<NavigationContainer>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === "Home") {
iconName = focused ? "ios-home" : "ios-home-outline";
}
{/*other routes*/}
})}
>
<Tab.Screen name="Home" component={HomeStackScreen} />
{/*other tabs*/}
</Tab.Navigator>
</NavigationContainer>
);
}
I'm fairly new to React Native and I have created a Drawer Navigator in my App.js file.
One of my navigation components is named LifeScreen.
I am trying to pass a function to update a state (setSavedQuotes) to LifeScreen so that in LifeScreen I can update the value of the state (savedQuotes). This is straightforward in React but I can't seem to be able to do the same in React Native.
App.js (Navigator)
const App = () => {
const [savedQuotes, setSavedQuotes] = useState([])
return (
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen
name="Home"
component={HomeScreen}
initialParams={{ sam: savedQuotes }}
/>
<Drawer.Screen
name="Life"
component={props => {
return <LifeScreen props={props} setSavedQuotes={setSavedQuotes} />
}}
/>
<Drawer.Screen name="Work" component={WorkScreen} />
<Drawer.Screen name="Saved" component={SavedScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
}
LifeScreen.js
const LifeScreen = ({ route, setSavedQuotes }) => {
const [quote, setQuote] = useState('')
const lifeWisdom = [...] // array of quotes
const getQuote = () => {... } // get Random Quote
const saveQuote = () => {
// console.log('life:', route.params)
console.log("function: ", setSavedQuotes); // returns undefined
}
return (
<View style={styles.mainContainer}>
<TouchableOpacity onPress={getQuote} style={styles.quoteContainer}>
<Text style={styles.quote}>{quote}</Text>
</TouchableOpacity>
<TouchableOpacity onPress={saveQuote} style={styles.imgContainer}>
<AntDesign name="hearto" size={40} color="black" />
</TouchableOpacity>
</View>
);
};
Whenever I console.log(setSavedQuotes), I get undefined.
I can pass props and the state value without any problem, as I did with HomeSCreen in App.js.
I tried the following: regarding props, initialParams, react-navigation and route. I can manage to pass the state with all of them, but not the function to update the state.
I've followed the documentation for creating bottom tab navigation with react-navigation v5 ("#react-navigation/native": "^5.2.3")
Currently is partially used this example in my project from docs https://reactnavigation.org/docs/bottom-tab-navigator/ to fit the needs of version 5.
Example might be following
// Navigation.tsx
import { BottomTabBarProps } from '#react-navigation/bottom-tabs';
import { TabActions } from '#react-navigation/native';
import * as React from 'react';
function Navigation({ state, descriptors, navigation }: BottomTabBarProps) {
return (
<View>
{state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const isFocused = state.index === index;
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
const jumpToAction = TabActions.jumpTo(options.title || 'Home');
navigation.dispatch(jumpToAction);
}
};
return (
<TouchableOpacity
key={options.title}
accessibilityLabel={options.tabBarAccessibilityLabel}
accessibilityRole="button"
active={isFocused}
activeOpacity={1}
testID={options.tabBarTestID}
onPress={onPress}
>
{route.name}
</TouchableOpacity>
);
})}
</View>
);
}
export default Navigation;
However, I have a couple of nested StackNavigators as described in AppNavigator.tsx
AppNavigator.tsx
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import React from 'react';
import { AppState, AppStateStatus } from 'react-native';
import Navigation from '../components/navigation/Navigation';
import AccountScreen from '../screens/account';
import SettingsScreen from '../screens/settings';
import SupportScreen from '../screens/support';
import HomeNavigator from './HomeNavigator';
import TransactionNavigator from './TransactionNavigator';
const { Navigator, Screen } = createBottomTabNavigator();
const AppNavigator = () => {
return (
<View>
<Navigator tabBar={(props) => <Navigation {...props} />}>
<Screen
component={HomeNavigator}
name="Home"
options={{ title: 'Home' }}
/>
<Screen
component={TransactionNavigator}
name="Transactions"
options={{
title: 'Transactions' }}
/>
<Screen
component={AccountScreen}
name="Account"
options={{ title: 'Account' }}
/>
<Screen
component={SupportScreen}
name="Support"
options={{ title: 'Support' }}
/>
<Screen
component={SettingsScreen}
name="Settings"
options={{
title: 'Settings' }}
/>
</Navigator>
</View>
);
};
export default AppNavigator;
And I am aiming for resetting the nested StackNavigator each time user leaves it. So example can be HOME -> TRANSACTIONS -> TRANSACTION_DETAIL (which is part of a nested navigator) -> HOME -> TRANSACTIONS
currently, I see a TRANSACTION_DETAIL after the last step of the "walk through" path. Nevertheless, I want to see TRANSACTIONS instead. I found that if I change
if (!isFocused && !event.defaultPrevented) {
const jumpToAction = TabActions.jumpTo(options.title || 'Home');
navigation.dispatch(jumpToAction);
}
to
if (!isFocused && !event.defaultPrevented) {
navigation.reset({ index, routes: [{ name: route.name }] });
}
it more or less does the thing. But it resets the navigation, so it is unmounted and on return back, all data are lost and need to refetch.
In navigation is PopToTop() function that is not available in this scope.
Also I tried to access all nested navigators through descriptors, yet I have not found how to correctly force them to popToTop.
And the idea is do it on one place so it will be handled automatically and there would not be any need to implement it on each screen.
I have tried with navigator.popToTop() but it was not working. It may be stackNavigator and TabNavigator having a different history with the routes. I have fixed the issue with the below code. "Home" is my stack navigator name and another "Home" is screen name (Both are same for me)
tabBarButton: props => (
<TouchableOpacity
{...props}
onPress={props => {
navigation.navigate('Home', {
screen: 'Home'
})
}}
/>
),