React Native: Passing navigation route as props into dynamically rendered component - javascript

I am building an app in which several of the screens have dynamically rendered cards that are mapped to an array of objects called ENTRIES. Each one of these cards can be pressed to navigate to a corresponding screen, however, I cannot seem to get the navigation to work.
I am passing is the screen value from ENTRIES as props from the Settings.js screen into the ClickableCard component, which then gets passed into the TouchableOpacity onClick as this.props.navigation.navigate(screen).
However I keep getting the following error TypeError: undefined is not an object (evaluating '_this3.props.navigation.navigate')
Here is an example of the code below:
App.js File
import React from 'react;
import {createMaterialBottomTabNavigator} from '#react-navigation/material-bottom-tabs';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import Home from './src/screens/Home';
import SettingsScreen from './src/screens/SettingsScreen';
import PrivacyScreen from './src/screens/PrivacyScreen';
import NotificationsScreen from './src/screens/NotificationsScreen';
import SoundsScreen from './src/screens/SoundsScreen';
import ThemeScreen from './src/screens/ThemeScreen';
const PrivacyStack = createStackNavigator();
const SettingsStack = createStackNavigator();
const AuthStack = createStackNavigator();
const MainStack = createStackNavigator();
const Tabs = createMaterialBottomTabNavigator();
const TabNavigator = () => {
return (
<Tabs.Navigator
initialRouteName="Home"
<Tabs.Screen
name="Home"
component={HomeStack}
/>
Tabs.Screen
name="Settings"
component={SettingsStack}
children={this.SettingsStack}
</Tabs.Navigator>
)
}
const AuthStack = () => (
<AuthStack.Navigator>
<AuthStack.Screen
name="Auth"
component={Auth}
/>
</AuthStack.Navigator>
);
const SettingsStackScreen = () => (
<SettingsStack.Navigator>
<SettingsStack.Screen
name="Settings"
component={Settings}
/>
<SettingsStack.Screen
name="Privacy"
component={PrivacyStack}
/>
<SettingsStack.Screen
name="Theme"
component={ThemeScreen}
/>
<SettingsStack.Screen
name="Notifications"
component={NotificationsScreen}
/>
<SettingsStack.Screen
name="Sound"
component={SoundsScreen}
/>
</SettingsStack.Navigator>
);
const PrivacyStack = () => (
<PrivacyStack.Navigator>
<PrivacyStack.Screen
name="Privacy"
component={PrivacyScreen}
/>
<PrivacyStack.Screen
name="Notifications"
component={NotificationsScreen}
/>
</PrivacyStack.Navigator>
);
const App = () => {
return (
<NavigationContainer ref={navigationRef}>
<MainStack.Navigator>
<MainStack.Screen name="Tabs" component={TabNavigator} />
<MainStack.Screen
name="Auth"
component={AuthStack}
options={{gestureEnabled: false}}
/>
</MainStack.Navigator>
</NavigationContainer>
)
}
Settings.js File
import React, {Component} from 'react';
import {TouchableOpacity, ScrollView} from 'react-native;
import ClickableCard from './ClickableCard'
export default class Settings extends Component {
render(screen, index) {
return (
<ScrollView>
{ENTRIES.map((entry, index) => (
<ClickableCard screen={entry.screen} key={entry.index}/>
))}
</ScrollView>
)
}
export default Settings
ClickableCard.js Component
import React, {Component} from 'react';
import {TouchableOpacity, ScrollView} from 'react-native;
export default class ClickableCard extends Component {
constructor(props) {
super(props);
}
render() {
const {
screen,
key
} = this.props
return (
<TouchableOpacity
key={key}
onPress={() => this.props.navigation.navigate(screen)}>
</TouchableOpacity>
)
}
}
entries.js File
import React from 'react';
export const ENTRIES = [
{
name: "Theme",
screen: "ThemeScreen",
},
{
name: "Sounds",
screen: "SoundsScreen",
},
{
name: "Notifications",
screen: "NotificationsScreen",
},
]

You are trying to access navigation outside the navigation stack.
If you are using a functional component you can go with the useNavigation hook but as this is a class based component you will have to send the navigation as a prop or you can do the below as suggested in the documentation
import { useNavigation } from '#react-navigation/native';
class ClickableCard extends Component {
constructor(props) {
super(props);
}
render() {
const { screen, key } = this.props;
return (
<TouchableOpacity
key={key}
onPress={() =>
this.props.navigation.navigate(screen)
}></TouchableOpacity>
);
}
}
const ClickableCardWithNavigation= (props) {
const navigation = useNavigation();
return <ClickableCard {...props} navigation={navigation} />;
}
export default connect(ClickableCardWithNavigation)(TodoList)

Related

React navigator not moving between screens

My app closes when I press the switch screen button. I don't see any error output.
I did a version check and there was no problem.
The function I wrote for the button works
Router.js
import React from "react";
import { NavigationContainer } from "#react-navigation/native";
import { createNativeStackNavigator } from "#react-navigation/native-stack";
import First from "./First";
import Second from "./Second";
const Stack = createNativeStackNavigator();
const Router = () =>{
return(
<NavigationContainer>
<Stack.Navigator initialRouteName="First">
<Stack.Screen name="First" component={ First }/>
<Stack.Screen name="Second" component={ Second }/>
</Stack.Navigator>
</NavigationContainer>
);
}
export default Router;
First.js
import React from 'react';
import {View,Text,Button} from 'react-native';
const First = ( props ) =>{
const gotoSecond = () => {
console.log("First");
props.navigation.navigate("Second");
console.log("second")
}
return (
<View>
<Text>Hello First Page!</Text>
<Button title="Go SecondPage!" onPress={gotoSecond}/>
</View>
);
};
export default First;
Second.js
import React from 'react';
import {View,Text} from 'react-native';
const Second = () =>{
return (
<View>
<Text>Hello Second Page!</Text>
</View>
);
};
export default Second;
OS:Android,
Emulator:Genymotion.
Now I'm using ADV instead of Genymotion, problem solved.

this.props.navigation.navigate() not working

I want the app to check if there's a user logged in (firebase authentication). If there is, navigate to one page (react navigation v5), if there isn't, to another.
I have the following code:
In App.js
//import navigation + screens
const Stack = createStackNavigator();
const MaterialBottomTabs = createMaterialBottomTabNavigator();
class App extends React.Component {
AuthStack = () => {
<Stack.Navigator>
<Stack.Screen name="login" component={LoginScreen} />
<Stack.Screen name="register" component={RegisterScreen} />
</Stack.Navigator>
}
createBottomTabs = () => {
return <MaterialBottomTabs.Navigator>
<MaterialBottomTabs.Screen name="first" component={firstScreen} />
<MaterialBottomTabs.Screen name="second" component={secondScreen} />
</MaterialBottomTabs.Navigator>
}
render(){
return(
<LoadingScreen />
)
}
}
export default App;
In Loading.js
import React from "react";
import { Text, SafeAreaView } from "react-native";
import * as firebase from "firebase";
import {AuthStack, createBottomTabs} from "./App.js";
class LoadingScreen extends React.Component {
componentDidMount() {
firebase.auth().onAuthStateChanged((user) => {
this.props.navigation.navigate(user ? createBottomTabs : AuthStack);
});
}
render() {
return (
<SafeAreaView>
<Text>
Waiting...
</Text>
</SafeAreaView>
);
}
}
export default LoadingScreen;
I get an error which says:
TypeError: undefined is not an object (evaluating '_this.props.navigation.navigate')
The navigation prop is not passed into all components, only screen components receive this prop automatically! If, however, you wish to access the navigation prop in any of your components, you may use the useNavigation hook.
Here is an example from the docs
import * as React from 'react';
import { Button } from 'react-native';
import { useNavigation } from '#react-navigation/native';
function MyBackButton() {
const navigation = useNavigation();
return (
<Button
title="Back"
onPress={() => {
navigation.goBack();
}}
/>
);
}

The component for route 'WELCOME' must be a React component

I'm kind of sure that the export-import is well done, because I use export default.
It was a working code when I had both the sign-up and log-in forms in the same page.
Now I want to navigate to two different pages through two buttons, and it gives me this error in loginIndex.js, that is the file in which I created the NavigationStack.
The fact is that it worked perfectly before adding the two new files to the stack, but now it tells me that 'Welcome' is not a React component.
Dependencies
src
/components
/screens
/welcome
/index.js
/sign_up
/SignUp.js
/login
/login.js
/routes
/loginStack.js
index.js
// #flow
import React, { Component } from 'react';
import {
TouchableOpacity,
StatusBar,
Animated,
FlatList,
Image,
View,
Text,
YellowBox,
} from 'react-native';
import Loading from '~/components/common/Loading';
import {withNavigation} from 'react-navigation';
import Navigation from '~/routes/loginStack'
import ROUTE_NAMES from '~/routes/loginStack'
const Welcome = ({navigation}) => {
onClickLoginButton = (): void => {
const navigation = this.props.navigation
navigation.navigate(ROUTE_NAMES.LOGIN)
};
onClickSignUpButton = (): void => {
const navigation = this.props.navigation
navigation.navigate(ROUTE_NAMES.SIGNUP)
};
onLoadBackgroundImage = (): void => {
this.setState({
isBackgroundImageLoaded: true,
});
};
return (
<Container>
<Navigation/>
<Loading />
<StatusBar
backgroundColor="transparent"
barStyle="light-content"
translucent
animated
/>
<BackgroundImage
onLoad={this.onLoadBackgroundImage}
/>
{isBackgroundImageLoaded && (
<Wrapper>
<TitleWrapper>
<Title></Title>
</TitleWrapper>
<NavigationTitleWrapper>
<TouchableOpacity
onPress={this.onClickLoginButton}
>
<Animated.Text
>
Accedi
</Animated.Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.onClickSignUpButton}
>
<Animated.Text>
Registrati
</Animated.Text>
</TouchableOpacity>
</NavigationTitleWrapper>
</Wrapper>
)}
</Container>
);
}
export default withNavigation(Welcome);
loginStack.js
import { createSwitchNavigator, createAppContainer } from 'react-navigation';
import Welcome from '../components/screens/welcome/index';
import MainStack from './mainStack';
import Login from '~/components/screens/log_in/Login';
import SignUp from '~/components/screens/sign_up/SignUp';
export const ROUTE_NAMES = {
LOGIN: 'LOGIN',
MAIN_STACK: 'MAIN_STACK',
WELCOME: 'WELCOME',
SIGNUP: 'SIGNUP',
};
const LoginStack = createSwitchNavigator(
{
[ROUTE_NAMES.WELCOME]: {
screen: Welcome,
},
[ROUTE_NAMES.LOGIN]: {
screen: True_login,
},
[ROUTE_NAMES.SIGNUP]: {
screen: SignUp,
},
[ROUTE_NAMES.MAIN_STACK]: {
screen: MainStack,
},
},
{
initialRouteName: ROUTE_NAMES.SPLASH,
},
);
const AppContainer = createAppContainer(LoginStack);
export default AppContainer;
You are using 'setState' but using functional comoponents.
You should use hooks instead..
In addition you use 'this' but again, relevant for classes..
You are adding typescript annotations but using javascript..
Try to change the index file lto this:
import React from 'react';
import {
TouchableOpacity,
StatusBar,
Animated
} from 'react-native';
import Loading from '~/components/common/Loading';
import { withNavigation } from 'react-navigation';
import Navigation from '~/routes/loginStack'
import ROUTE_NAMES from '~/routes/loginStack'
const Welcome = ({ navigation }) => {
const [isBackgroundImageLoaded, setIsBackgroundImageLoaded] = React.useState(false);
onClickLoginButton = () => {
navigation.navigate(ROUTE_NAMES.LOGIN)
};
onClickSignUpButton = () => {
navigation.navigate(ROUTE_NAMES.SIGNUP)
};
onLoadBackgroundImage = () => {
setIsBackgroundImageLoaded(true);
};
return (
<Container>
<Navigation />
<Loading />
<StatusBar
backgroundColor="transparent"
barStyle="light-content"
translucent
animated
/>
<BackgroundImage
onLoad={onLoadBackgroundImage}
/>
{isBackgroundImageLoaded && (
<Wrapper>
<TitleWrapper>
<Title></Title>
</TitleWrapper>
<NavigationTitleWrapper>
<TouchableOpacity
onPress={onClickLoginButton}
>
<Animated.Text
>
Accedi
</Animated.Text>
</TouchableOpacity>
<TouchableOpacity
onPress={onClickSignUpButton}
>
<Animated.Text>
Registrati
</Animated.Text>
</TouchableOpacity>
</NavigationTitleWrapper>
</Wrapper>
)}
</Container>
);
}
export default withNavigation(Welcome);

How to implement details screen that dynamically displays detail of pokemon when a user presses on a picture in react native?

So I have built a pokemon application that would display pokemon with their images and details. On the homescreen, I have it so that the first 20 pokemon are shown as a preview. Then if the user wants to learn more about the pokemon, they can press on the pokemon and it would bring the user to the "PokeDetails" screen. However, when I press on the pokemon, it brings me to the "PokeDetails" page but the page is blank. Except for the text that I explicitly render from the "PokeDetails" screen. I have the code for both screens below. Any help is appreciated. Thank you
//Home.js
import React, { useState } from "react";
import { View, Text , Button, FlatList, ActivityIndicator, TouchableOpacity, Image } from "react-native";
import { GlobalStyles } from "../styles/GlobalStyles";
import PokeDetails from "./PokeDetails";
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
dataSource: [],
}
}
componentDidMount() {
fetch(`https://pokeapi.co/api/v2/pokemon/?limit=20`)
.then((res)=> res.json())
.then((response)=> {
this.setState({
isLoading: false,
dataSource: response.results,
})
console.log("RESPONSE",response)
console.log("RESPONSE.RESSSULTS",response.results)
})
}
render() {
const showIndicator = this.state.isLoading == true ? <ActivityIndicator size="large" color="#0000ff" /> : null;
return(
<View style={GlobalStyles.container}>
<View style={GlobalStyles.activityIndicator}>{showIndicator}</View>
<FlatList
numColumns={1}
data={this.state.dataSource}
renderItem={({item})=>
<TouchableOpacity onPress={()=> this.props.navigation.navigate("PokeDetails", {item} )}>
<PokeDetails imageUrl={`https://projectpokemon.org/images/normal-sprite/${item.name}.gif`} name={item.name} item={item} />
</TouchableOpacity>
}/>
<Button onPress={()=> this.props.navigation.navigate("About")} title="Go to about"/>
</View>
)
}
}
export default Home;
// PokeDetails.js
import React from "react";
import { View, Text , Image, Button} from "react-native";
import {GlobalStyles} from "../styles/GlobalStyles";
import { TouchableOpacity } from "react-native-gesture-handler";
import { useRoute } from '#react-navigation/native';
const PokeDetails =({name, imageUrl, detail, route})=> {
return(
<View style={GlobalStyles.container}>
<Text>This text is written expilictly</Text>
<Text>{route.params.item}</Text>
</View>
)
}
export default PokeDetails;
// Root.js
import React from "react"
import { createStackNavigator } from "#react-navigation/stack";
import Home from "../screens/Home";
import PokeDetails from "../screens/PokeDetails";
import { NavigationContainer } from '#react-navigation/native';
const Root =() => {
const Stack = createStackNavigator();
return(
<Stack.Navigator>
<Stack.Screen name="Home" component={Home}/>
<Stack.Screen name="PokeDetails" component={PokeDetails}/>
</Stack.Navigator>
)
}
export default Root;
// App.js
import 'react-native-gesture-handler';
import React from 'react';
import { View , StyleSheet } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createDrawerNavigator } from "#react-navigation/drawer";
import About from "./screens/About";
import Root from "./Route/Root";
import PokeDetails from "./screens/PokeDetails"
const App =()=> {
const Drawer = createDrawerNavigator();
return(
<View style={styles.container}>
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen name="Home" component={Root}/>
<Drawer.Screen name="About" component={About}/>
</Drawer.Navigator>
</NavigationContainer>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1
}
})
export default App;
I don't know if i understood your question. You already pass an item props to your PokeDetails component from your Homescreen, but there is no item in the props your receive in it. just add it and i guess you will access to your data and will be able to display it:
const PokeDetails =({name, imageUrl, detail, item})=> {
return(
<View style={GlobalStyles.container}>
<Image source={{uri: imageUrl}} style={{height: 50, width: 50}}/>
<Text style={GlobalStyles.pokeText}>{name}</Text>
<Text>This text is explicitly written</Text>
// add your stuff here...
<Text>{item.something}</Text>
</View>
)
}
By the way, since you already have the item prop, you can remove the name which already comes from item :)

Export named arrow function got "Object(...) is not a function" error

In a React app I wrote a function in file1.js and use this function in file2.js
// file1.js
export const withPrefix = (Component) => (props) => (
<PrefixContext.Consumer>
{prefix => <Component {...props} prefix={prefix}/>}
</PrefixContext.Consumer>
)
// file2.js
import { withPrefix } from '/path/to/file1.js'
let Toolbar = withPrefix(({prefix}) => ( // !error happens here
<Fragment>
<div style={{flexGrow: 1}}>
<Button><Link to={`${prefix}/create`}>New Artifact</Link></Button>
</div>
<Search style={{width: 200}}/>
</Fragment>
))
Then I got the error "TypeError: Object(...) is not a function". So I changed export withPrefix function
export function withPrefix(Component) {
return (props) => (
<PrefixContext.Consumer>
{prefix => <Component {...props} prefix={prefix}/>}
</PrefixContext.Consumer>
)
}
And the error is gone, everything works. But I wonder why these two exports result differently?
And another question is if I want to export an arrow function in es6, is the 2nd export function style the only method?
Attachment 1 (DefaultView.js):
import React, {Component} from 'react'
import {Layout} from 'antd'
import Toolbar from './Toolbar'
import Content from './Content'
export const PrefixContext = React.createContext()
export function withPrefix(Component) {
return (props) => (
<PrefixContext.Consumer>
{prefix => <Component {...props} prefix={prefix}/>}
</PrefixContext.Consumer>
)
}
export default class DefaultView extends Component {
constructor(props) {
super(props)
this.state = {
view: props.defaultView
}
}
handleViewChange = (view) => {
this.setState({view})
}
render() {
const {prefix, views} = this.props
const {view} = this.state
return (
<PrefixContext.Provider value={prefix}>
<Layout>
<Toolbar view={view} views={views} onViewChange=
{this.handleViewChange}/>
<hr/>
<Content view={view}/>
</Layout>
</PrefixContext.Provider>
)
}
}
Attachment 2 (Summary.js)
import React, {Component, Fragment} from 'react'
import {Button, Input} from 'antd'
import {Link} from 'react-router-dom'
import ArtifactTable from './ArtifactTable'
import {withPrefix} from "./DefaultView"
const {Search} = Input
export const Toolbar = withPrefix(({prefix}) => (
<Fragment>
<div style={{flexGrow: 1}}>
<Button><Link to={`${prefix}/create`}>新建软件包</Link></Button>
</div>
<Search style={{width: 200}}/>
</Fragment>
))
class Summary extends Component {
state = {
data: []
}
componentDidMount() {
const {prefix} = this.props
console.log('prefix=' + prefix)
fetch(prefix).then(json => {
this.setState({data: json.content})
})
}
render() {
const {data} = this.state
return (
<div>
<ArtifactTable data={data}/>
</div>
)
}
}
export default withPrefix(Summary)
Attachment 3 (Toolbar.js)
import React from 'react'
import {Switch, Route} from 'react-router-dom'
import {Toolbar as SummaryToolbar} from './Summary'
import Create from './Create'
import Details from './Details'
import Edit from './Edit'
import {withPrefix} from "./DefaultView"
const Toolbar = withPrefix(({prefix, view, onViewChange}) => (
<div style={{background: '#fff', padding: 16, display: 'flex',
alignItems: 'baseline'}}>
<Switch>
<Route exact path={`${prefix}`} component={SummaryToolbar}/>
<Route exact path={`${prefix}/create`}
component={() => <Create.Toolbar view={view} onViewChange=
{onViewChange}/>}/>
<Route exact path={`${prefix}/:id`}
component={() => <Details.Toolbar view={view} onViewChange=
{onViewChange}/>}/>
<Route exact path={`${prefix}/:id/edit`}
component={() => <Edit.Toolbar view={view} onViewChange=
{onViewChange}/>}/>
</Switch>
</div>
))
export default Toolbar
Update It's indeed the cyclic dependency problem as #Bergi and #loganfsmyth said. After I moved out
the withPrefix export snippet into a new file Context.js from DefaultView.js, the problem resolved. But I still have one quesion. In a cyclic dependency circumstances, why export const f = () => () => {} different from export function f() => { return () => {} }. Is export const lazy evaluated than export function as #loganfsmyth said?

Categories

Resources