How can I create dynamic routes/ screens with React Native? - reactjs

I want to create dynamic routes similar to the way you can with react-router using the slash colon as so:
<Route exact path="/user/:_id" component={UserPage} />
How does this work with react native if I want to open a page specif to a link?
const Item = ({ title }) => (
<View style={styles.item}>
<Button
onPress={() => {
alert(`You tapped ${title}`);
}}
title={title}
>
<Text style={styles.title}>{title}</Text>
</Button>
</View>
);
const MainPage = () => (
<SafeAreaView style={styles.container}>
<SectionList
sections={DATA}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => <Item title={item} />}
renderSectionHeader={({ section: { title } }) => (
<Text style={styles.header}>{title}</Text>
)}
/>
</SafeAreaView>
);
export default MainPage;
function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
<Stack.Screen name="MainPage" component={MainPage} />
<Stack.Screen name="MediaScreen" component={MediaScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
I want the Item onPress to go to its own page/screen

As for as I know react native navigation doesnt allow url params, you have to pass parameters when you call the navigate function.
const Item = ({ title, navigation }) => (
<View style={styles.item}>
<Button
onPress={() => {
alert(`You tapped ${title}`);
navigation.navigate('Details', { _id })
}}
title={title}
>
<Text style={styles.title}>{title}</Text>
</Button>
</View>
);
const MainPage = ({ navigation }) => (
<SafeAreaView style={styles.container}>
<SectionList
sections={DATA}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => <Item title={item} navigation={navigation}/>}
renderSectionHeader={({ section: { title } }) => (
<Text style={styles.header}>{title}</Text>
)}
/>
</SafeAreaView>
);
export default MainPage;
Then in Details it will have a route parameter which you can get _id from route.params._id
const Details = ({ navigation, route }) => {
const [_id] = useState(route.params._id);
return (
<></>
)
};

With react-navigation you can pass parameters in two ways:
1 - Pass params to a route by putting them in an object as a second parameter to the navigation.navigate function: navigation.navigate('RouteName', { /* params go here */ })
2 - Read the params in your screen component: route.params.
Full documentation https://reactnavigation.org/docs/params

Related

Calling function of class based component from another component in react native

I have two components one is Messages and other component is Navigation it's a stack navigator component. I would like to call function named onRefresh of Messages component from component Navigation header buttons. Please see my code how can I achieve this.
Messages.js (component file)
export default class Messages extends Component {
// Constructor
constructor(props) {
super(props)
this.state = {
messages: [],
isLoading: true
};
}
// End Constructor
// Getting Messages from server
async getMessages() {
try {
const response = await fetch('https://reactnative.dev/movies.json');
const json = await response.json();
this.setState({ messages: json.movies });
} catch (error) {
console.log(error);
} finally {
this.setState({ isLoading: false });
}
}
// End Getting messages from server
componentDidMount() {
this.getMessages();
}
// On refresh the messages
onRefresh = async () => {
this.setState({isLoading: true,}, () => {this.getMessages();});
}
// Send now sms using button.
sendNowMessage = async (title) => {
Alert.alert(title, "asdasdas");
}
render() {
const { messages, isLoading } = this.state;
return (
<SafeAreaView style={styles.container}>
{isLoading ? <ActivityIndicator size="large" color="#0000ff" style={ styles.horizontal } /> : (
<FlatList
data={ messages }
keyExtractor={({ id }, index) => id}
onRefresh={() => this.onRefresh()}
refreshing={this.state.isLoading}
renderItem={({ item }) => (
<View style={styles.item}>
<Text style={{ marginBottom: 12, }}>{item.title}, {item.releaseYear}</Text>
<Button
onPress={() => this.sendNowMessage(item.title)}
title="Send SMS"
style={styles.sendSMS}
color="#841584"
accessibilityLabel="Learn more about this purple button"
/>
</View>
)}
/>
)}
</SafeAreaView>
);
}
}
Navigation.js (component file)
const Stack = createStackNavigator();
export default function Navigation() {
return (
<Stack.Navigator>
<Stack.Screen
name="AuthScreen"
component={AuthScreen}
options={{
title: "Welcome",
}}
/>
<Stack.Screen
name="Messages"
component={Messages}
options={{
headerTitle: 'Messages',
headerRight: () => {
return (
<View style={styles.headerButtons}>
<View style={{ marginRight:10, }}>
<TouchableOpacity
onPress={() => {
new Messages().onRefresh() <---- I am calling it like this.
}}
>
<Text
style={styles.actionButtons}
>
<Ionicons name="reload-sharp" size={20} color="black" />
</Text>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity
onPress={() => {
alert("working")
}}
>
<Text
style={styles.actionButtons}
>
<Ionicons name="filter" size={20} color="black" />
</Text>
</TouchableOpacity>
</View>
</View>
)
}
}}
/>
</Stack.Navigator>
);
}
I am calling onRefresh function like this new Messages().onRefresh() and getting the following error.
Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the Messages component.
I want to learn what is the best way to call onRefresh function.

The action 'NAVIGATE' with payload {"name":"RestaurantDetail"} was not handled by any navigator. Do you have a screen named 'RestaurantDetail'?

I could not fix this bug. It says RestaurantDetail was not handled by any navigator and asks me if I do you have a screen named RestaurantDetail? The idea is to get a restaurant detail card from the bottom of the app. Any help will be appreciated
export const RestaurantsScreen = ({navigation}) => {
const {isLoading, restaurants} = useContext(RestaurantContext);
return (
<SafeArea>
{isLoading && (
<LoadingContainer>
<Loading
size={50}
style={{marginLeft: -25}}
animating={true}
color={Colors.blue300}
/>
</LoadingContainer>
)
}
<Search />
<RestaurantList
data={restaurants}
renderItem={({item}) => {
return(
<Pressable onPress={() => navigation.navigate("RestaurantDetail")}>
<Spacer position="bottom" size="large">
<RestaurantInfoCard restaurant={item} />
</Spacer>
</Pressable>
)}
}
keyExtractor={(item) => item.name}
/>
</SafeArea>
)
};
const RestaurantStack = createStackNavigator();
export const RestaurantsNavigator = () => {
return (
<RestaurantStack.Navigator headerMode="none">
<RestaurantStack.Screen
name="Restaurants"
component={RestaurantsScreen}
/>
<RestaurantStack.Screen
name="RestaurantDetail"
component={() => <Text>Restaurant Detail</Text>}
/>
</RestaurantStack.Navigator>
);
};

Can't use hooks in FlatList renderItem

Please! Can somebody explain me whats wrong with following code. I'm trying to pass ListItem
export const ListItem: ListRenderItem<IUser> = ({item}) => {
return (
<RNEListItem onPress={() => {}}>
<Avatar source={{uri: item.picture.thumbnail}} rounded size="medium" />
<RNEListItem.Content>
<RNEListItem.Title>{`${item.name.first} ${item.name.last}`}</RNEListItem.Title>
<RNEListItem.Subtitle>{item.email}</RNEListItem.Subtitle>
</RNEListItem.Content>
<RNEListItem.Chevron size={30} />
</RNEListItem>
);
};
to renderItem prop in FlatList
return (
<FlatList
data={users}
renderItem={ListItem}
ItemSeparatorComponent={ListItemSeparator}
keyExtractor={keyExtractor}
showsVerticalScrollIndicator={false}
ListFooterComponent={ListLoader}
onEndReached={handleMore}
onEndReachedThreshold={0.1}
onRefresh={handleRefresh}
refreshing={isRefreshing}
/>
);
everything fine. But when I'm trying to use hooks
export const ListItem: ListRenderItem<IUser> = ({item}) => {
const {navigate} = useNavigation<RootStackParamList>();
const handlePress = useCallback(() => {
console.log(item.login.uuid);
navigate(ERootStackScreens.USER_SCREEN, {id: item.login.uuid});
}, []);
return (
<RNEListItem onPress={() => {}}>
<Avatar source={{uri: item.picture.thumbnail}} rounded size="medium" />
<RNEListItem.Content>
<RNEListItem.Title>{`${item.name.first} ${item.name.last}`}</RNEListItem.Title>
<RNEListItem.Subtitle>{item.email}</RNEListItem.Subtitle>
</RNEListItem.Content>
<RNEListItem.Chevron size={30} />
</RNEListItem>
);
};
RN return's
Hooks can only be called inside the body of a function component.
but when i change renderItem this way
return (
<FlatList
data={users}
renderItem={()=>ListItem}
ItemSeparatorComponent={ListItemSeparator}
keyExtractor={keyExtractor}
showsVerticalScrollIndicator={false}
ListFooterComponent={ListLoader}
onEndReached={handleMore}
onEndReachedThreshold={0.1}
onRefresh={handleRefresh}
refreshing={isRefreshing}
/>
);
Everything becomes fine. But it looks like types in docs are incorrect. Cuz according to them first example should work without problems
renderItem: ListRenderItem<ItemT> | null | undefined;
export type ListRenderItem<ItemT> = (info: ListRenderItemInfo<ItemT>) => React.ReactElement | null;
You need to pass the props in the functional component:
renderItem={(props)=><ListItem {...props} />}

React Naitve Navigation: How to show a modal from drawer?

Is there a way to show a modal from drawer in React Native?
There was a similar question and I changed my code following the answer.
My current code is like this.
MyRootStackNavigator.tsx
const Stack = createStackNavigator<RootStackParamList>();
const MyRootStackNavigator = () => {
return (
<Stack.Navigator mode="modal">
<Stack.Screen
name="Main"
component={MyDrawerNavigator}
options={{ headerShown: false }}
/>
<Stack.Screen
name="MyModal"
component={MyModal}
/>
</Stack.Navigator>
);
};
MyDrawerNavigation.tsx
const Drawer = createDrawerNavigator();
const MyDrawerNavigator = () => {
return (
<Drawer.Navigator>
<Drawer.Screen
name="Home"
component={MyStackNavigator}
/>
<Drawer.Screen
name="About this app"
component={About}
/>
</Drawer.Navigator>
);
}
But for this code the section to show a modal doesn't appear on drawer. Only Home and About this app sections appear on drawer. How can I set the section to show a modal on drawer?
You can use a CustomDrawerContent which has a modal inside and use a button to open the Modal
function CustomDrawerContent(props) {
const [modalVisible, setModalVisible] = useState(false);
return (
<DrawerContentScrollView {...props}>
<Modal
style={{ flxe: 1, backgroundColor: 'red' }}
visible={modalVisible}
onRequestClose={() => setModalVisible(false)}>
<Text>Modal Content Here</Text>
<Button title="Close" onPress={() => setModalVisible(false)} />
</Modal>
<DrawerItemList {...props} />
<DrawerItem
label="Open Modal"
onPress={() => {
setModalVisible(true);
}}
/>
</DrawerContentScrollView>
);
}
And the DrawerNavigator should be setup like this
<Drawer.Navigator
drawerContent={(props) => <CustomDrawerContent {...props} />}>
You can refer the docs here
https://reactnavigation.org/docs/drawer-navigator/#providing-a-custom-drawercontent
If you want to continue with the code that you have put in the question, you can do like below to navigate to the about screen (Instead of having the modal inside the content)
<DrawerItem
label="Open Screen"
onPress={() => {
props.navigation.navigate('About')
}}
/>

Navigate to item from FlatList and send the entire item in

I have this notes that I am rendering in a FlatList and I want to navigate to the item that is tapped. How can i send in with the navigation the entire item instead of doing id={id} name={name} etc, is it possible to navigate to that item and send to the view the entire item?
class MyNotes extends Component {
render() {
const { notes, navigate } = this.props;
return (
<View style={styles.view}>
<FlatList
numColons={notes.length}
data={notes}
renderItem={({ item: { id, name } }) => {
return (
<View>
<Note
name={name}
id={id}
navigate={navigate}
/>
</View>
);
}}
keyExtractor={item => item.id}
/>
</View>
);
}
}
export default Notes;
Create an onPress handler for your note item and in that you can pass your note item to your view
renderItem={({ item }) => {
return (
<View>
<Note
name={item.name}
id={item.id}
navigate={navigate}
onPress={() => navigate('Note', {...item})}
/>
</View>
);
}}
Yes, you could just pass using the spread operator. So in your case, it would be
<Note {...item} />

Resources