Navigate to another component on clicking FlatList in React - javascript

I am using FlatList to display Image and Text side by side in a Component screen. I want to click on any row and open a new Component class[FoodItems] and passing just a simple string.
It says, "Cannot read properties of undefined (reading 'navigate')".
I have installed all required packages.
npm install react-navigation
npm install #react-navigation/native
I don't know what this.props here is? I am not sending props from previous screen. I just copy paste it from some post.
Restaurants.js:
import { View, FlatList, Text, TouchableOpacity } from 'react-native';
import { useNavigation } from '#react-navigation/native';
import withRouter from './withRouter';
class Restaurants extends Component {
render() {
if (!this.state.restaurantsSet) {
this.setState({ restaurantsSet: true });
this.settingRestaurants(this.props.location.state.station);
}
return (
<View>
<div className='header'></div>
<FlatList
ItemSeparatorComponent={this.FlatListItemSeparator}
ListHeaderComponent={this.FlatListHeader}
style={{ flex: 1 }}
data={this.state.restaurants}
renderItem={({ item }) =>
<TouchableOpacity
onPress={() =>
this.props.navigation.navigate(
'FoodItems',
{ message: 'my_message' }
)
}
>
<Row {...item} />
</TouchableOpacity>
}
/>
</View>
)
}
}
export default withRouter(Restaurants);
withRouter.js:
import { useLocation, useParams, useNavigation } from 'react-router-dom';
const withRouter = WrappedComponent => props => {
const location = useLocation();
const params = useParams();
const navigation = useNavigation();
return (
<WrappedComponent
{...{props, params}}
{...{ location, params,}}
{...{navigation, params}}
/>
);
};
export default withRouter;

The withRouter HOC is implemented incorrectly. The useNavigation hook is a RRDv6.4 Data router only hook. Emphasis mine.
This hook tells you everything you need to know about a page
navigation to build pending navigation indicators and optimistic UI on
data mutations. Things like:
Global loading indicators
Disabling forms while a mutation is happening
Adding busy indicators to submit buttons
Optimistically showing a new record while it's being created on the server
Optimistically showing the new state of a record while it's being updated
Important
This feature only works if using a data router, see Picking a Router
import { useNavigation } from "react-router-dom";
function SomeComponent() {
const navigation = useNavigation();
navigation.state;
navigation.location;
navigation.formData;
navigation.formAction;
navigation.formMethod;
}
The useNavigation hook isn't used to issue any imperative navigation actions. For this you should import and use the useNavigate hook as this is the hook that returns the navigate function.
import { useLocation, useParams, useNavigate } from 'react-router-dom';
const withRouter = WrappedComponent => props => {
const location = useLocation();
const params = useParams();
const navigate = useNavigate();
return (
<WrappedComponent
{...props}
{...{ location, params, navigate }}
/>
);
};
export default withRouter;
The in the React Class component access this.props.navigate. Don't forget that data you want to pass in route state is passed in the option object's state property.
import { View, FlatList, Text, TouchableOpacity } from 'react-native';
import withRouter from './withRouter';
class Restaurants extends Component {
...
componentDidMount() {
if (!this.state.restaurantsSet) {
this.setState({ restaurantsSet: true });
this.settingRestaurants(this.props.location.state?.station);
}
}
...
render() {
return (
<View>
<div className='header'></div>
<FlatList
ItemSeparatorComponent={this.FlatListItemSeparator}
ListHeaderComponent={this.FlatListHeader}
style={{ flex: 1 }}
data={this.state.restaurants}
renderItem={({ item }) =>
<TouchableOpacity
onPress={() =>
this.props.navigate(
'FoodItems',
{ state: { message: 'my_message' }}
);
}
>
<Row {...item} />
</TouchableOpacity>
}
/>
</View>
)
}
}
export default withRouter(Restaurants);

Related

How to use react-router-dom v6 navigate in class component

I installed react-router-dom v6 and I want to use a class based component, in previous version of react-router-dom v5 this.props.history() worked for redirect page after doing something but this code not working for v6 .
In react-router-dom v6 there is a hook useNavigate for functional component but I need to use it in class base component , Please help me how to use navigate in class component ?
In the react-router-dom v6, the support for history has been deprecated but instead of it, navigate has been introduced. If you want to redirect user to a specific page on success of a specific event, then follow the steps given below:
Create a file named as withRouter.js, and paste the code given below in this file:
import { useNavigate } from 'react-router-dom';
export const withRouter = (Component) => {
const Wrapper = (props) => {
const navigate = useNavigate();
return (
<Component
navigate={navigate}
{...props}
/>
);
};
return Wrapper;
};
Now, in whichever class based component you want to redirect the user to a specific path/component, import the above withRouter.js file there and use this.props.navigate('/your_path_here') function for the redirection.
For your help, a sample code showing the same has been given below:
import React from 'react';
import {withRouter} from '.your_Path_To_Withrouter_Here/withRouter';
class Your_Component_Name_Here extends React.Component{
constructor(){
super()
this.yourFunctionHere=this.yourFunctionHere.bind(this);
}
yourFunctionHere()
{
this.props.navigate('/your_path_here')
}
render()
{
return(
<div>
Your Component Code Here
</div>
)
}
}
export default withRouter(Your_Component_Name_Here);
Above Code works Perfect. And this is just a small extension.
If you want onclick function here is the code:
<div className = "row">
<button className= "btn btn-primary"
onClick={this.yourFunctionHere}>RedirectTo</button>
</div>
in class base component for redirect user follow this step :
first import some component like this
import { Navigate } from "react-router-dom"
now make a state for Return a boolean value like this:
state = {
redirect:false
}
now insert Naviagate component to bottom of your component tree
but use && for conditional rendring like this :
{
this.state.redirect && <Navigate to='/some_route' replace={true}/>
}
now when you want redirect user to some page just make true redirect state
on a line of code you want
now you can see you navigate to some page :)
Try this:
import {
useLocation,
useNavigate,
useParams
} from "react-router-dom";
export const withRouter = (Component) => {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
<Component
{...props}
router={{ location, navigate, params }}
/>
);
}
return ComponentWithRouterProp;
}
and just used this function, in my case:
import { withRouter } from '../utils/with-router';
import './menu-item.styles.scss';
const MenuItem = ({title, imageUrl, size, linkUrl,router}) =>(
<div
className={`${size} menu-item`} onClick={() => router.navigate(`${router.location.pathname}${linkUrl}`)}
>
<div className='background-image'
style={{
backgroundImage: `url(${imageUrl})`
}} />
<div className="content">
<h1 className="title">{title.toUpperCase()}</h1>
<span className="subtitle">SHOP NOW</span>
</div>
</div>
)
export default withRouter(MenuItem);
I found this solution here https://www.reactfix.com/2022/02/fixed-how-can-i-use-withrouter-in-react.html
Other solution is useNavigate, for example:
<button onClick={() => {navigate("/dashboard");}} >
Dashboard
</button>
In a react class component use <Navigate>. From the react router docs:
A <Navigate> element changes the current location when it is rendered. It's a component wrapper around useNavigate, and accepts all the same arguments as props.
Try creating a reusable functional Component like a simple button and you can use it in your class component.
import React from "react";
import { useNavigate } from "react-router-dom";
const NavigateButton = ( { buttonTitle, route,isReplaced}) => {
const navigate = useNavigate();
return (
<button
className = "btn btn-primary"
onClick = { () => {
navigate( route , {replace:isReplaced} )
}}
>
{buttonTitle}
</button>;
);
});
export default NavigateButton;
After this, you can use NavigateButton in any of your class Components. And it will work.
<NavigateButton title = {"Route To"} route = {"/your_route/"} isReplaced = {false}/>
Found this explanation from the GitHub react-router issue thread, this explained how to use react-router 6 with class components
https://github.com/remix-run/react-router/issues/8146
I got this code from the above issue explanation
import React,{ Component} from "react";
import { useNavigate } from "react-router-dom";
export const withNavigation = (Component : Component) => {
return props => <Component {...props} navigate={useNavigate()} />;
}
//classComponent
class LoginPage extends React.Component{
submitHandler =(e) =>{
//successful login
this.props.navigate('/dashboard');
}
}
export default withNavigation(LoginPage);
If you need to use params for data fetching, writing a logic in your ClassComponent and render component depending on them, then create wrapper for your ClassComponentContainer
import { useLocation, useParams } from 'react-router-dom';
import ClassComponentContainer from './ClassComponentContainer';
export default function ClassComponentWrap(props) {
const location = useLocation();
const params = useParams();
return <ClassComponentContainer location={location} params={params} />
}
after it just use params in ClassComponent which is in props
import React from 'react';
import { connect } from 'react-redux';
import axios from 'axios';
import PresentationComponent from './PresentationComponent';
class ClassComponent extends React.Component {
componentDidMount() {
let postID = this.props.params.postID;
axios.get(`https://jsonplaceholder.typicode.com/posts/${postID}`)
.then((response) => {console.log(response)})
}
render() {
return <PresentationComponent {...this.props} />
}
}
const mapStateToProps = (state) => {...}
const mapDispatchToProps = (dispatch) => {...}
const ClassComponentContainer = connect(mapStateToProps, mapDispatchToProps)(ClassComponent);
export default ClassComponentContainer;
and use ClassComponentWrap component in Route element attribute
import { BrowserRouter, Route, Routes } from "react-router-dom";
import ClassComponentWrap from './components/ClassComponentWrap';
export default function App(props) {
return (
<BrowserRouter>
<Routes>
<Route path="/posts/:postID?" element={<ClassComponentWrap />} />
</Routes>
</BrowserRouter>
);
}
Here is my solution:
import React, { Component } from "react";
import { useNavigate } from "react-router-dom";
class OrdersView extends Component {
Test(props){
const navigate = useNavigate();
return(<div onClick={()=>{navigate('/')}}>test{props.test}</div>);
}
render() {
return (<div className="">
<this.Test test={'click me'}></this.Test>
</div>);
}
}

Using a function with "navigate" as a compponent

I am trying to use navigation in react native, but it gives me an error when I try use a component with navigate (LogIn) with the
<Login/>
tags.
It says that navigate is undified so I passed the navigation as a prop with no success
App.js
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { SafeAreaView, StyleSheet, Text, View } from 'react-native';
import Login from "./assets/code/Login.js";
import { NavigationContainer, StackActions } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import 'react-native-gesture-handler';
const Stack = createStackNavigator();
export default function App({ navigation }) {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Welcome to dog app, and I hate react"
component={HomePage}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
function HomePage({ navigation }) {
return (
<View style={styles.container}>
<View>
<Text> {""}Welcome to dogappcoolapp app</Text>
</View>
<View style={styles.blue}>
<Login navigate={ navigation } style={styles.blue} />
</View>
</View>
)
}
The error is in this line
<Login navigate={ navigation } style={styles.blue} />
The error is
The LogIn function is in
LogIn.js
import React, { useState } from 'react';
import { StyleSheet, Text, View, Button, Script, Alert } from 'react-native';
import { NavigationContainer, StackActions, } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import 'react-native-gesture-handler';
const Stack = createStackNavigator();
export function Login({navigation}) {
if (true)
return (
navigation.navigate('WelcomePage')
);
}
function WelcomePage () {
return (
<View>
<Button title="enter" />
<Text> dog app dog woof-app{"\n\n\n\n\n\n\n\n\n"}OMG!! YOU ARE LOGGED IN! WELCOME!{"\n\n\n\n\n"}</Text>
</View>
);
}
const styles = StyleSheet.create({
blue: {
flex: 2,
backgroundColor: "dodgerblue",
alignItems: 'center',
justifyContent: 'center',
},
});
export default Login;
If I remove all of the navigation prop and tags from the function LogIn, then I can use LogIn as a componnent with <LogIn/>, but not with navigation, I tried usinng it with
navigate={ navigation }
(As it is in the code that I posted)
and I tried without it, I keep getting similar errors.
How can I use LogIn as a component with </> tags while still having navigation component in it?
The logic is correct, in child components, you can pass a navigation prop to get access to navigation, but you are passing your navigation object to a prop called navigate <Login navigate={ navigation } style={styles.blue} />, no wonder it's undefined, you should receive it as navigate in your Login component.
export function Login({navigation}) { //<-- here you have navigation where the prop name that you pass is navigate.
so it should be
export function Login({navigate}) {
...
navigate.navigate('...')
or you should rename your prop to navigation and then your navigation.navigate won't be undefined anymore.

Nested switchNavigator inside a component with React Navigation

Im stuck with a Nested Navigation System. I have a main navigator (switchNavigator) and one of its screens is a component witch inside has some views and inside of one of this views I want to put another navigator (switchNavigator), but I get this error "The navigation prop is missing for this navigator. In react-navigation v3 and v4 you must set up your app container directly.". I really don't know if it's posible or a valid implementation.
This is my mainNavigator and two simple components, the second one is where I call the nested navigator:
import React from 'react';
import {createAppContainer,createSwitchNavigator} from 'react-navigation';
import { View, Text } from 'react-native';
import NestedNav from './nestedNav';
const FirstView = props=>{
return(
<View>
<Text>First view</Text>
</View>
)
}
const secondView = props=>{
return(
<View>
<View>
<Text>Second view</Text>
</View>
<View>
<NestedNav></NestedNav>
</View>
<View>
<Text>some other ui content</Text>
</View>
</View>
)
}
const MainNavigator = createSwitchNavigator(
{
firstView:FirstView,
secondView:secondView
},
{
initialRouteName: 'secondView',
}
);
export default createAppContainer(MainNavigator)
And this is my NestedNavigator:
import React from 'react';
import {createAppContainer,createSwitchNavigator} from 'react-navigation';
const NestedNav = createSwitchNavigator(
{
firstView:SomeFirstViewInNestedNav,
secondView:SomesecondViewInNestedNav
},
{
initialRouteName: 'firstView',
}
);
export default NestedNav
as you rendered the nestedNavigator it should be in wrapped into a appContainer.
const NestedNav = createSwitchNavigator(
{
firstView:SomeFirstViewInNestedNav,
secondView:SomesecondViewInNestedNav
},
{
initialRouteName: 'firstView',
}
);
--> export default createAppContainer(NestedNav)
I got the solution, I just had to create a static reference of the nested Navigation's Router in my nested navigator container component (in the example "secondView" component):
static router = NestedNav.router
And set the navigation prop to the nested navigator:
<NestedNav navigation = {this.props.navigation}></NestedNav>
This solution is in the Docs: here

FlatList does not render any text in react-native

I'm fairly new to react native and redux and was trying to render the library title from a JSON file in a flat list using redux, but my FlatList component does not render anything on the screen.
here's my code :
LibraryList.js
import React, { Component } from "react";
import { FlatList } from "react-native";
import { connect } from "react-redux";
import ListItem from "./ListItem";
class LibraryList extends Component {
renderItem(library) {
return <ListItem library={library} />;
}
render() {
return (
<FlatList
data={this.props.libraries}
renderItem={this.renderItem}
keyExtractor={library => library.id}
/>
);
}
}
const mapStateToProps = state => {
return { libraries: state.libraries };
};
export default connect(mapStateToProps)(LibraryList);
ListItem.js
import React, { Component } from "react";
import { Text } from "react-native";
import { CardSection } from "./common";
class ListItem extends Component {
render() {
return (
<CardSection>
<Text>{this.props.library.title}</Text>
</CardSection>
);
}
}
export default ListItem;
App.js
import React from "react";
import { View } from "react-native";
import { Provider } from "react-redux";
import { createStore } from "redux";
import reducers from "./reducers";
import { Header } from "./components/common";
import LibraryList from "./components/LibraryList";
const App = () => {
return (
<Provider store={createStore(reducers)}>
<View>
<Header headerText="Tech Stack" />
<LibraryList />
</View>
</Provider>
);
};
export default App;
The JSON file is like
[
{
"id": '' ,
"title": '' ,
"description":''
},
{
"id":'' ,
"title":'' ,
"description":''
}
]
I read some solutions for this suggesting changing the renderItem function to something like this
renderItem = ({ library }) => <ListItem library={library} />
still does not work. Can someone help me with this problem?
Thanks.
You have to make your renderItem as an arrow function. Otherwise you have to bind your function inside constructor in order to access function as renderItem={this.renderItem}.
import React, { Component } from 'react';
import { FlatList } from 'react-native';
import { connect } from 'react-redux';
import ListItem from './ListItem';
class LibraryList extends Component {
renderItem = ({ item }) => {
return <ListItem library={item} />
}
render() {
return (
<FlatList
data={this.props.libraries}
renderItem={this.renderItem}
keyExtractor={library => library.id}
/>
);
}
}
const mapStateToProps = state => {
return { libraries: state.libraries };
};
export default connect(mapStateToProps)(LibraryList);
or you can call your renderItem as an arrow function inside render like below
renderItem={(item) => this.renderItem(item)}
but using an arrow function in render creates a new function each time the component renders, which may break optimizations based on strict identity comparison.
Hope this helps you. Feel free for doubts.
In your flatlist try thi s:
<FlatList
data={this.props.libraries}
renderItem={({item, index}) => {
this.renderItems(item); // change this name to renderItems so that it doesnt clash with flatlist default renderItem
}}
/>
Hope it helps. feel free for doubts
You have several approaches to your problem.
Firstly your renderItem should be binded, so either do this
renderItem = (library) => {
or this
renderItem={this.renderItem.bind(this)}
besides the binding problem, flatlist prop renderItem will return to your function an object with this structure
{ item, index }
so in reality your renderItem should be like this
renderItem({ item }){
return <ListItem library={item} />;
}

Firebase and stack nav both cant export in app.js at the same time

I have a Stack Navigator and some firebase functions in my app.js file. After implementing the Stack I am unable to use the log out button in the listScreen component because I found I cant export multiple in app.js. If I remove one export for app.js for example the Stack will work and vice versa.
App.js problem
//export navigation, container to wrap root navigator
export default createAppContainer(Switcher);
//PROBLEM cant have multiple exports, ether the Switcher or App class can export Individually
//class for app
export default class App extends Component {
Here is all the code in app.js
//Blue List is has been created by Ameer Yasin and Nick
import React, { Component } from 'react';
import { StyleSheet, Text, View, SafeAreaView } from 'react-native';
//firebase
import firebase from './servers/firebase.js';
//navigation
import { createStackNavigator } from 'react-navigation-stack';
import { createAppContainer } from 'react-navigation';
//components and sub component
import Login from './components/LoginPage.js';
import { SpinLoad } from './components/common/index.js';
import ListScreen from './components/ListScreen';
import AboutScreen from './components/AboutScreen';
// switch stack navigator
const Switcher = createStackNavigator(
{
//from listScreen to about screen
TaskPg: ListScreen,
AboutPg: AboutScreen
},
{
//the route of nav
initialRouteName: "TaskPg",
//header title
defaultNavigationOptions: {
title: 'BlueList'
}
}
)
//export navigation, container to wrap root navigator
export default createAppContainer(Switcher);
//PROBLEM cant have multiple exports, ether the Switcher or App class can export Individually
//class for app
export default class App extends Component {
//are u logged in set state to null
state = { loggedIn: null };
componentWillMount() {
//when logged in call this function
firebase.auth().onAuthStateChanged((user) => {
//logged in
if (user) {
this.setState({ loggedIn: true });
} //logged out
else {
this.setState({ loggedIn: false });
}
});
}
renderContent() {
//render content depending on auth status
switch (this.state.loggedIn) {
//goto list screen when logged in
case true: return (
<ListScreen />
)
//if not logged in goto log in page
case false: return <Login />;
//show loading icon by default
default: return <SpinLoad size='large' />
}
}
//render content on screen
render() {
return (
//SafeAreaView container for content
<SafeAreaView style={styles.container}>
{this.renderContent()}
</SafeAreaView>
);
}
};
//styles
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Here is the list screen where the Stack Navigator is. Again, the navigator works only if I remove the export for App in app.js therefore the sigh out wont work... I need both to work.
const ListScreen = props => {
return (
<View style={styles.container}>
{/* add task component with date picker */}
<AddItemModel />
{/* button pressed to goto About Screen */}
<TouchableOpacity onPress={() => props.navigation.navigate('AboutPg')}>
<Text style={styles.aboutBtn}>About App</Text>
</TouchableOpacity>
{/* sign out button linked to firebase log out */}
<TouchableOpacity onPress={() => firebase.auth().signOut()} >
<Text style={styles.button} >Sign Out</Text>
</TouchableOpacity>
</View>
);
}
I would really appreciate some help, I tried many methods for multiple exports as well as moving the firebase login functions in another component and still had the same issue.
UPDATE
Here is what was suggested, I tried to add the import statement with the correct path and it wont work.
import React from 'react';
import firebase from '../servers/firebase';
import { Text, StyleSheet, View, TouchableOpacity } from 'react-native';
import AddItemModel from './common/AddItemModel';
//what was suggested
import {default as AppNavigator, App} from "../App"
const ListScreen = props => {
return (
<View style={styles.container}>
{/* add task component with date picker */}
<AddItemModel />
{/* button pressed to goto About Screen */}
<TouchableOpacity onPress={() => props.navigation.navigate('AboutPg')}>
<Text style={styles.aboutBtn}>About App</Text>
</TouchableOpacity>
{/* sign out button linked to firebase log out */}
<TouchableOpacity onPress={() => firebase.auth().signOut()} >
<Text style={styles.button} >Sign Out</Text>
</TouchableOpacity>
</View>
);
}
File directory structure screenshot. The two relivent files are App.js and ListScreen.js
You can absolutely have multiple exports, you just can't have multiple default exports. Try import {default as AppNavigator, App} from "app.js".
So something like:
// app.js
export default createAppContainer(Switcher);
export class App extends Component {}
// component.js
import {default as AppNavigator, App} from "app.js"
const MyComponent = () => (
<div>
<AppNavigator/>
<App/>
</div>
);

Categories