I have a FlatList inside of a screen that uses BottomTabNavigator from react-navigation-bottom-tabs. Inside of the FlatList are swipeable items, using Swipeable from react-native-gesture-handler. Inside of the tab screens these items aren't swipeable for some reason. However, when I put the screen with the FlatList inside of a StackNavigator screen, and nest that inside of the BottomNavigator tabscreen, it does work. I'm at a loss for why it works this way but not the other way. Does anyone have the slightest clue why this is the case?
Edit: I've seen the issue of not being able to navigate bottom tabs using swipe. I'm not trying to navigate tabs though. Swipeable is applied to items inside a FlatList, that is inside its own component, that is rendered by the Home screen, which in turn is used by the navigator.
The TabsNavigator:
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" }}
/>
),
tabBarActiveTintColor: "#104543",
}}
/>
)
}
The HomeStack (StackNavigator):
export default function StackNavigator() {
const Stack = createStackNavigator();
return (
<Stack.Navigator>
<Stack.Screen
options={{ headerShown: false }}
name="Home"
component={Home}
/>
</Stack.Navigator>
);
}
The Home screen:
export const Home = ({ navigation }) => {
const dispatch = useDispatch();
useEffect(() => {
const fetchUserData = async () => {
const data = await axios.get(
`http://10.0.2.2:80/
`,
{
auth: {
username: "***",
password: "***",
},
}
);
dispatch(setUsersData(data.data));
};
return navigation.addListener("focus", async () => {
await fetchUserData();
});
}, [navigation]);
return (
<View style={styles.container}>
<SearchBarHeader />
//The component with the FlatList in question that contains swipeable items
<SwipeableFlastList />
</View>
);
};
The preferred outcome would be to place the Home screen inside of the Tab.Screen component like this:
<Tab.Screen
name="List"
component={Home}
/>
Per request, the Swipeable component. It's inside of another ListItem component, which inturn is inside a component that renders the flatlist:
<KeyboardAvoidingView style={styles.container}>
<Swipeable
renderRightActions={(
progress: Animated.AnimatedInterpolation,
dragAnimatedValue: Animated.AnimatedInterpolation
) => renderRightActions(progress, dragAnimatedValue, item.Id)}
renderLeftActions={(
progress: Animated.AnimatedInterpolation,
dragAnimatedValue: Animated.AnimatedInterpolation
) => renderLeftActions(progress, dragAnimatedValue, item.Id)}
useNativeAnimations={true}
onSwipeableOpen={() => setSwipeOpen(!swipeOpen)}
onSwipeableClose={() => setSwipeOpen(!swipeOpen)}
>
<TouchableWithoutFeedback>
<AccordionListItem item={item} />
</TouchableWithoutFeedback>
</Swipeable>
</KeyboardAvoidingView>
Related
I have a React Native app that I am adding navigation to. I have two different stacks I want to navigate between. I have searched SO and came up with the two versions which I have in my below code. The setup is that I have a main stack navigator which has a drawer stack and a normal stack (Auth) that I'd like to be able to go between. The main things are redirecting on login and logout from the header of the Drawer stack.
Below is my code, I am looking for help to figure out how to enable the mentioned navigation that I need the app to do. In theory, this is a common thing so I am pretty sure it should be supported and documented though I can't seem to find anything documented on it.
Error message
The action 'NAVIGATE' with payload {"name":"Auth","params":{}} was not handled by any navigator.
Do you have a screen named 'Auth'?
HeaderRight
export default function HeaderRight ({ navigation }) {
const { removeAuth } = useContext(AuthContext);
const logout = () => {
removeAuth()
// Note both are uncommented for sharing and are not actually meant to be when running the app
navigation.navigate('Auth', { screen: 'Login' }); // { screen: 'Login' }
navigation.navigate(
'Auth',
{},
NavigationActions.navigate({
routeName: 'Login'
})
)
}
return (
<View style={styles.header}>
<Icon name='logout' onPress={logout} size={28} style={styles.icon} color="white" />
</View>
)
}
Root navigation
<Stack.Navigator
initialRouteName={"Splash"}
screenOptions={{}}
component={SplashScreen}
>
{ checkAuth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerRight: () => (
<HeaderRight navigation={navigation}/>
),
headerTitleAlign: 'center',
headerTintColor: 'white',
headerStyle: {
backgroundColor: '#5742f5'
},
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
</Stack.Navigator>
drawer stack
export const DrawerStack = (props) => {
return (
<Drawer.Navigator
initialRouteName="Lists"
>
<Drawer.Screen
name="Conversations"
options={{
title: props.title,
}}
component={ListScreen}
/>
<Drawer.Screen
name="Room"
options={{
drawerLabel: () => null,
title: null,
drawerIcon: () => null,
}}
component={RoomScreen}
/>
</Drawer.Navigator>
);
};
auth stack
export const AuthStack = () => {
return (
<Stack.Navigator
initialRouteName={'Login'}
>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Forgot" component={ForgotScreen} />
</Stack.Navigator>
);
};
Edit
I think I have found the potential cause for this but I have no clue about the solution to it.
I have this in my root navigator (same as above)
{ checkAuth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerLeft: () => (
<HeaderLeft />
),
headerRight: () => (
<HeaderRight navigation={navigation}/>
),
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
This means that the stack/screen for Auth is only being rendered/exists when the drawer stack isn't shown. How can I refactor my root navigator to fix this?
I am thinking I need to somehow force the checkAuth function to re-render based on different actions taken or to watch a certain local storage.
This is the code for checkAuth
const checkAuth = () => {
try {
const authData = globalStorage.getString('accessToken')
if(authData !== null && authData !== undefined) {
setAuth(authData)
return true
} else {
return false
}
} catch(e) {
console.error(e)
return false
}
}
Context on what globalStorage is
import { MMKV } from 'react-native-mmkv'
export const globalStorage = new MMKV()
I have my navigators arranged like this.
Bottom Navigator
2. Stack Navigator
3. Material Top Tabs Navigator
4. Home Screen
I have created headerRight button in StackNavigator's ScreenComponent like this.
export default HomeDashboardNavigation = ({route, navigation}) => {
return (
<DashboardStack.Navigator>
<DashboardStack.Screen name="TopTabNavigator" component={TopTabNavigator}
options={{
headerShown: true,
headerShadowVisible: false,
headerStyle:{backgroundColor:'#FEF8F0'},
headerTitle: (props) => <LogoTitle {...props} />,
headerTitleAlign: 'center',
headerRight: () => (
<TouchableOpacity onPress={() => {
// Need to pass a boolean value to HomeScreen
}}
>
<HintComponent />
</TouchableOpacity>
),
}}
/>
</DashboardStack.Navigator>
)
}
My TopTabNavigator looks like this:
const TopTabNavigator = ({route, navigation}) => {
return (
<Tab.Navigator tabBar={props => <TabBar {...props} />} >
<Tab.Screen name="HomeScreen" component={HomeScreen} />
<Tab.Screen name="Second" component={SecondScreen} />
<Tab.Screen name="Third" component={ThirdScreen} />
<Tab.Screen name="Fourth" component={FourthScreen} />
</Tab.Navigator>
)
}
My HomeScreen is:
const HomeScreen = (props) => {
const isHeaderRightClicked = false; //Need to get the value here
return (
<ScrollView>
</ScrollView>
);
}
I am using react native navigation 6. I tried using setParams and setting route.params and tried to get is in HomeScreen using useEffect on props.navigation but it never gets executed.
I don't know how to pass value from onPress event to the screen. HomeScreen is embedded in TopTabNavigator which is a Screen component of StackNavigator whose header button click sets the value.
Could you please suggest a good way to do this.
Try something like my example below. Here a useState boolean hook [isClicked, setIsClicked] is defined, where the onPress-function changes this boolean on every press. The state isClicked in then passed through the TopTabNavigator to the HomeScreen where it can be used.
HomeDashboardNavigation file:
export default HomeDashboardNavigation = ({ route, navigation }) => {
const [isClicked, setIsClicked] = useState(false);
return (
<DashboardStack.Navigator>
<DashboardStack.Screen
name="TopTabNavigator"
options={{
headerShown: true,
headerShadowVisible: false,
headerStyle: { backgroundColor: "#FEF8F0" },
headerTitle: (props) => <LogoTitle {...props} />,
headerTitleAlign: "center",
headerRight: () => (
<TouchableOpacity
onPress={() => {
setIsClicked((state) => !state);
}}
>
<HintComponent />
</TouchableOpacity>
),
}}
>
{(props) => <TopTabNavigator isClicked={isClicked} {...props} />}
</DashboardStack.Screen>
</DashboardStack.Navigator>
);
};
TopTabNavigator file
const TopTabNavigator = ({ isClicked, route, navigation }) => {
return (
<Tab.Navigator tabBar={(props) => <TabBar {...props} />}>
<Tab.Screen name="HomeScreen">
{(props) => <HomeScreen isClicked={isClicked} {...props} />}
</Tab.Screen>
<Tab.Screen name="Second" component={SecondScreen} />
<Tab.Screen name="Third" component={ThirdScreen} />
<Tab.Screen name="Fourth" component={FourthScreen} />
</Tab.Navigator>
);
};
HomeScreen file
export const HomeScreen = (props) => {
const isHeaderRightClicked = props.isClicked;
return <ScrollView></ScrollView>;
};
I have created a DrawerNavigator in my react native app which looks like this.
I just dont like the default header that react- native gives. So I wanna access it through an icon. I guess also using the onPress condition
import { createStackNavigator } from '#react-navigation/stack';
import { createDrawerNavigator } from '#react-navigation/drawer';
// importing of all screens
const Drawer = createDrawerNavigator();
const DrawerContent = () => {
return (
<Drawer.Navigator>
<Drawer.Screen
name="Home"
component={CategoryStack}
/>
<Drawer.Screen name="Aboutus" component={Aboutus} />
<Drawer.Screen name="Interest Recieved" component={InterestRecieved} />
</Drawer.Navigator>
);
};
const Stack = createStackNavigator();
const MainStack = () => {
return (
<Stack.Navigator>
<Stack.Screen
name="Loading"
component={Loading}
options={{ headerShown: false }}
/>
</Stack.Navigator>
);
};
export default MainStack;
How do I open it using an onPress of an icon?
Thanks!
React navigation useNavigation hook expose drawer actions - toggleDrawer,openDrawer and closeDrawer event handlers which you can use to open or close drawer.
import React from "react";
import { View, Text, StyleSheet, Pressable } from "react-native";
import { useNavigation } from "#react-navigation/native";
const ToggleDrawer = () => {
const { toggleDrawer,closeDrawer,openDrawer } = useNavigation();
return (
<Pressable onPress={toggleDrawer}>{/** Add your Icon Here */}</Pressable>
);
};
You can check in-depth drawer example
In the header options you can customize the header and add for example an icon on the top left side like this.
useLayoutEffect(() => {
navigation.setOptions({
title: 'ScreenName',
headerLeft: () => (
<View style={{ marginLeft: 15 }}>
<TouchableOpacity onPress={() => navigation.openDrawer()} >
{*INSERT ICON HERE*}
</TouchableOpacity>
</View>
),
})
})
What I want to achieve is when a user clicks on a screen to navigate to another screen, I want the transitioning between the screens to be delayed for 1 second or 2seconds.
I don't know if this is possible in
React navigation
.
I have this code that will transition from home to profile screen.
import * as React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const Stack = createStackNavigator();
const MyStack = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'Welcome' }}
/>
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
And this is the home screen code
const HomeScreen = ({ navigation }) => {
return (
<Button
title="Go to Jane's profile"
onPress={() =>
navigation.navigate('Profile', { name: 'Jane' })
}
/>
);
};
And this is the profile screen code.
const ProfileScreen = ({ navigation, route }) => {
return <Text>This is {route.params.name}'s profile</Text>;
};
};
Is possible to move from home screen to profile screen with a delay in time
As #nithinpp suggested, You could achieve this by adding a setTimeout for 2s in the Home screen button onClick.
const HomeScreen = ({ navigation }) => {
return (
<Button
title="Go to Jane's profile"
onPress={() => {
setTimeout(()=> {
navigation.navigate('Profile', { name: 'Jane' });
}, 2000);
}}
/>
);
};
I don't really see a reason to use this code in any application, as this is not at all a good practice to have a hardcoded delay in your application code.
i still trying to understand this react navigation 5.0.
Fyi i'm using expo, and right now no problem when navigate from one page to other,
problem is when i put navigation for the headerRight.
i put in headerRight in Stack.Navigator because i want this button to be accessible from other screen.
So basically the problem is,
i want to put logout button in headerRight,
but when i try to put navigation.navigate it sait undefined is not an object (evaluating '_this.props')
Then i try to call a function (handleClick),
problem is undefined is not an object too.
May i know what's wrong with my code?
Below is my full code :
import * as React from 'react';
import { Button, View, Text, TextInput, Image, StyleSheet } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import LoginScreen from './src/pages/auth/Login';
import HomeScreen from './src/pages/auth/HomeScreen';
const Stack = createStackNavigator();
export default function App() {
// handleClick = () => {
// this.props.navigation.navigate('Login');
// }
return (
<NavigationContainer>
<Stack.Navigator mode="modal" initialRouteName="Login" screenOptions={{
headerStyle: {
backgroundColor: '#f4511e',
},
headerRight: () => (
<Button
// only alert is ok, the other is error.
// onPress={() => alert('Success Logout!')}
onPress={this.handleClick}
// onPress={this.props.navigation.navigate('Home')}
title="Logout"
color="#fff"
/>
),
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}>
<Stack.Screen name="Login"
name="Login"
component={LoginScreen}
options={{
title: 'Simple Scorecard',
}} />
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
title: 'Home Menu',
}}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
Thanks before
You should try this.
You receive the reference to the router (navigation) by converting the property screenOptions to a function.
screenOptions={({route, navigation}) => ({ // get reference to navigation
headerRight: () => (
<Button
onPress={() => navigation.navigate('Home'); // call .navigate on navigation
/>
)
})}
My answer for that.
you have to pass "navigation", and convert "options" to the function
<Stack.Screen
// some code ...
options={({ navigation }) => ({
headerRight: () => (
<TouchableOpacity onPress={() => navigation.navigate("pageYouWantNavigateTo")} >
// some component ...
</TouchableOpacity>
),
})}
/>