React Native - React Navigation Cannot use Hooks in DrawerContent - reactjs

I am developing an e-commerce application using React Native and I am trying to use useState in the drawerContent and it tells me this
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
Thank you in advance for your answers.
Here's the code
import React, { useState } from 'react'
import { View, Text, TouchableOpacity, FlatList, StyleSheet, StatusBar } from 'react-native'
import IonIcons from "react-native-vector-icons/Ionicons"
import { categories } from '../../../services/DataTest'
import DrawerSearch from './DrawerSearch'
import DrawerItem from './DrawerItem'
export default function DrawerContent (props) {
const [search, setSearch] = useState("");
return (
<View>
<TouchableOpacity
style={styles.customDrawerTouch}
>
<View style={styles.backButtonRow}>
<IonIcons
name="ios-arrow-back"
size={25}
style={styles.customDrawerIcon}
color="#666666"
/>
<Text style={{ color: '#666666' }}>Back to Components</Text>
</View>
</TouchableOpacity>
<DrawerSearch value={search} setValue={setSearch}/>
<FlatList
data={categories}
keyExtractor={(item, index) => index.toString()}
renderItem={DrawerItem}
/>
</View>
);
}
const styles = StyleSheet.create({
customDrawerTouch: {
marginTop: StatusBar.currentHeight,
paddingLeft: 13,
paddingTop: 15,
},
customDrawerIcon: {
paddingRight: 10
},
backButtonRow: {
flexDirection: 'row',
alignItems: 'center',
paddingBottom: 17,
paddingLeft: 3,
borderBottomColor: '#F0F0F0',
borderBottomWidth: 1,
},
});
I'm using this component here
import * as React from 'react';
import { View, StyleSheet, StatusBar } from 'react-native';
import { createDrawerNavigator } from '#react-navigation/drawer';
import HeaderCategorie from '../../components/categories/index/HeaderCategorie';
import SearchBar from '../../components/home/index/SearchBar';
import DrawerContent from '../../components/categories/index/DrawerContent';
const Drawer = createDrawerNavigator();
function CategoriesScreen({ navigation }) {
return (
<View style={styles.container}>
<HeaderCategorie navigation={navigation}/>
<View style={styles.headerSearch}>
<SearchBar />
</View>
</View>
)
}
export default function Categories() {
return (
<Drawer.Navigator initialRouteName="Categories"
drawerContent={DrawerContent}
screenOptions={{headerShown:false}}
>
<Drawer.Screen name="Categories" component={CategoriesScreen} />
</Drawer.Navigator>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "flex-start",
alignItems: "center",
marginTop: StatusBar.currentHeight,
},
headerSearch: {
marginVertical:10
},
headerSearchText: {
fontWeight:"bold",
fontSize:35,
marginLeft:20,
marginVertical:15,
}
});

Reason: By using drawerContent={DrawerContent}, you are actually passing the reference of the DrawerContent function, which ends up breaking rules of hooks.
So to resolve this, change the following line:
<Drawer.Navigator initialRouteName="Categories"
drawerContent={DrawerContent}
screenOptions={{headerShown:false}}
>
to this
<Drawer.Navigator initialRouteName="Categories"
drawerContent={(props)=> <DrawerContent {...props}/>} // here
screenOptions={{headerShown:false}}
>
demo snack

Related

cannot read property 'map' of undefined react native

image errors
The error Cannot read property 'map' of undefined' is thrown when the map function in the Product component is executed.
I'm following the react Native tutorial, and I keep running into an issue when passing the value from the state of one component into another component.
I'm following the react Native tutorial, and I keep running into an issue when passing the value from the state of one component into another component.
i used axios to get the data
ProductScreen
import React, {useEffect, useState} from 'react';
import {View} from 'react-native';
import MenuProduct from '../components/Product/MenuProduct';
import MainHeader from '../components/Header/MainHeader';
import {POPULAR, Top_Sell} from '../data';
import ProductItem from '../components/Product/ProductItem';
import instance from '../routes/instance';
const ProductScreen = () => {
const [product, setProduct] = useState([]);
useEffect(() => {
const fetchData = async () => {
const data = await instance('/api/products', {
method: 'GET',
});
setProduct(data);
};
fetchData();
}, []);
return (
<View style={{flex: 1}}>
<MainHeader />
<MenuProduct list={Top_Sell} />
<ProductItem list={product} />
</View>
);
};
export default ProductScreen;
ProductItem
import React from 'react';
import ProductCard from './ProductCard';
const ProductItem = ({product}) => {
return (
<>
{product.map((item, index) => {
return (
<ProductCard
id={item._id}
image={item.image}
title={item.title}
price={item.price}
item={item}
key={index}
/>
);
})}
</>
);
};
export default ProductItem;
ProductCard
import React from 'react';
import {Image, ScrollView, Text, TouchableOpacity, View} from 'react-native';
import {colors, sizes, spacing} from '../../constants/theme';
import AddItem from '../../utils/AddItem';
const CardHeight = 220;
const ProductCard = ({props}) => {
return (
<ScrollView>
return (
<View
style={{
marginLeft: spacing.l,
marginBottom: spacing.l,
marginRight: spacing.l,
}}>
<View>
<View
style={{
backgroundColor: colors.white,
borderRadius: sizes.radius,
shadowColor: colors.black,
shadowRadius: 4,
shadowOpacity: 0.1,
shadowOffset: {width: 0, height: 2},
}}>
<TouchableOpacity
style={{
borderRadius: sizes.radius,
overflow: 'hidden',
flexDirection: 'row',
}}>
<Image
style={{
borderRadius: sizes.radius,
width: 160,
height: CardHeight - 60,
resizeMode: 'cover',
}}
source={props.image}
/>
<View style={{marginTop: spacing.l}}>
<View style={{marginLeft: spacing.l, marginBottom: spacing.s}}>
<Text style={{fontSize: 16, color: '#FA4A0C'}}>
{props.title}
</Text>
</View>
<View style={{marginLeft: spacing.l}}>
<Text style={{fontSize: 14, color: '#8b8989'}}>
{props.price}
</Text>
</View>
<TouchableOpacity style={{marginLeft: 130}}>
<AddItem />
</TouchableOpacity>
</View>
</TouchableOpacity>
</View>
</View>
</View>
); })
</ScrollView>
);
};
export default ProductCard;
I have tried several ways on stackoverflow but I can't figure it out
It should be list in ProductItem component and use list.map because when you sending prop in ProductItem you are sending list
import React from 'react';
import ProductCard from './ProductCard';
const ProductItem = ({list}) => {
return (
<>
{list.length > 0 && list.map((item, index) => {
return (
<ProductCard
id={item._id}
image={item.image}
title={item.title}
price={item.price}
item={item}
key={index}
/>
);
})}
</>
);
};
export default ProductItem;
Check the below syntax for passing data from parent to child component.
<Child parentToChild={data}/>
Here, we are passing the data in the child component as data.
data is the data we have to pass, and parentToChild is the prop's name.
Next, it's time to capture the data in the child component. And it's very simple.
Here, there can be two cases.
Case 1: If you are using a functional component, simply catch the parentToChild in the parameters.
import React from 'react'
export default function Child({parentToChild}) {
return (
<div>
{parentToChild}
</div>
)
}
Case 2: If you have a class component, then just use this.props.parentToChild.
import React, { Component } from 'react'
export default class Child extends Component {
render() {
return (
<div>
{this.props.parentToChild}
</div>
)
}
}

navigation.navigate is not a function. (In 'navigation.navigate("HomeScreen")', 'navigation.navigate' is undefined)

I m very new in react native and I m getting this error. Please help!
navigation.navigate is not a function. (In 'navigation.navigate("HomeScreen")', 'navigation.navigate' is undefined)
import { View, Text,Button,StyleSheet, TouchableOpacity } from 'react-native'
import React, {useState} from 'react'
import { NavigationContainer,CommonActions, useNavigation } from '#react-navigation/native';
const GetOtpButton = (navigation) => {
return (
<View >
<TouchableOpacity style = {styles.button} onPress={() => navigation.navigate("HomeScreen") } >
<Text style = {styles.text}>Log in</Text>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
button: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: "white",
width: "100%",
height: 50,
borderColor: "#E13C72",
backgroundColor: "#E13C72",
borderWidth: 0.1,
borderRadius: 80,
// marginBottom: 40,
// marginVertical: 5,
// marginTop: 10,
},
text: {
justifyContent: 'center',
textAlign: 'center',
color: "white",
fontWeight: 'bold'
}
});
export default GetOtpButton
---------------------App.js------------------
import react from "react";
import { StatusBar } from "expo-status-bar";
import { SafeAreaView, StyleSheet, Text, View, Dimensions } from "react-native";
import SigninScreen from "./src/SigninScreen/SigninScreen";
import HomeScreen from "./src/HomeScreen/HomeScreen";
import { NavigationContainer, StackActions } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="SigninScreen" options={{headerShown: false}}>
<Stack.Screen name = 'SigninScreen' component={SigninScreen} options={{headerShown: false}}/>
<Stack.Screen name = 'HomeScreen' component={HomeScreen} options={{headerShown: false}}/>
</Stack.Navigator>
</NavigationContainer>
);
}
const styles = StyleSheet.create({
root: {},
container: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
backgroundColor: "#FFFFFF",
},
});
The component GetOptButton is not defined as a screen in the navigator, thus the navigation object will not be passed to it automatically by the navigation framework. Thus, you have multiple choices here.
Define it as a screen inside the navigator
<Stack.Screen name = 'GetOpt' component={GetOptButton} />
The framework will now pass the navigation object to the GetOptButton component. You can access it as follows.
const GetOtpButton = ({navigation}) => { ... }
Now, since GetOptButton seems to be a component rather than a screen to which you will navigate, it might not make much sense to define it inside the navigator.
Pass the navigation object from a screen which is defined in the navigator that uses the GetOptButton component
// this works, since `Homescreen` is defined as a screen in your navigator
const HomeScreen = ({navigation}) => {
return (
<GetOptButton navigation={navigation} />
)
}
Destructure the navigation object inside GetOptButton as shown above.
Use the useNavigation hook
For some components it might not make sense to pass the navigation object from its parent, e.g. if the component is deeply nested. For this cases you can use the useNavigation hook.
const GetOptButton = () => {
const navigation = useNavigation()
}
You are not showing how GetOtpButton is used but guessing that navigation is passed as prop to GetOtpButton you need to get to navigation like this
const GetOtpButton = ({navigation}) => {
return (
<View >
<TouchableOpacity style = {styles.button} onPress={() => navigation.navigate("HomeScreen") } >
<Text style = {styles.text}>Log in</Text>
</TouchableOpacity>
</View>
)
}
See that the difference is that navigation is surrounded by curly brackets, so later you get the parameter navigation inside the props

I am getting maximum depth exceeded while running react native code

import { StyleSheet, Text, View, ImageBackground, SafeAreaView } from 'react-native';
import react, {useState} from 'react';
import InputComponent from './sharedComponent/InputComponent';
import ButtonComponent from './sharedComponent/ButtonComponent';
export default function App() {
const[text, setText] = useState('');
const[todoList, setToDoList] = useState([]);
return (
<ImageBackground
source={require('./assets/todoimages.png')}
style={{width: '100%', height: '100%'}}
>
<SafeAreaView>
<View style={styles.container}>
<InputComponent onchangeValue={setText}
/>
<ButtonComponent
addItem={setToDoList}
itemToAdd={text}
/>
</View>
<View>
{todoList.length > 0 && todoList.map((value) => <Text>{value}</Text>)}
</View>
</SafeAreaView>
</ImageBackground>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'baseline',
justifyContent: 'center',
paddingBottom:'10%'
},
});
I am getting the error :
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
can anyone tell me why I am getting the error and what is the solution for that?
below is the button component:
import {View,Button, StyleSheet} from 'react-native';
const ButtonComponent = (props) =>{
return(
<View style={styles.container}>
<View style={styles.buttonContainer}>
<Button
onPress={props.addItem(prevArray=> [...prevArray, props.itemToAdd])}
title="ADD"
/>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
buttonContainer: {
margin: 20
},
})
export default ButtonComponent;
below is the input component:
import { Text, TextInput, View } from 'react-native';
const InputComponent = (props)=>{
return(
<TextInput
style={{height: 40, backgroundColor:'white'}}
placeholder="add the item in to do list"
onChangeText={newText => props.onchangeValue(newText)}
/>
)
}
export default InputComponent;
You must use arrow-functions in the onPress method of your buttonComponent like this:
onPress={()=>props.addItem(...)}

Too much space between the appbar and status bar

I am making an app, where I want to use react navigation.
For some reason, whenever I use drawers in react navigation, there is a huge space between the status bar and the drawer app bar.
Here is my code for the ho -
import { StatusBar } from "expo-status-bar";
import * as React from "react";
import { useState, useEffect } from "react";
import { StyleSheet, Text, View, ScrollView } from "react-native";
import Constants from "expo-constants";
import Header from "../Header";
import { Flexbox } from "../Layout";
import Card from "../Card";
import { marginAboveCard } from "../../constants/constants";
import Row from "../Row";
import { convertToIndianNumberingFormat } from "../../utils/utils";
const HomeScreen = ({ navigation }) => {
const [cardsData, setCardsData] = useState({
confirmed: 0,
active: 0,
recovered: 0,
deceased: 0,
tests: 0,
critical: 0,
});
const [lastUpdated, setLastUpdated] = useState("");
useEffect(() => {
const getData = async () => {
const cardsDataResponse = await fetch(
"https://disease.sh/v3/covid-19/all"
);
const cardsDataFetched = await cardsDataResponse.json();
setCardsData({
confirmed: convertToIndianNumberingFormat(
cardsDataFetched.cases
),
active: convertToIndianNumberingFormat(cardsDataFetched.active),
recovered: convertToIndianNumberingFormat(
cardsDataFetched.recovered
),
deceased: convertToIndianNumberingFormat(
cardsDataFetched.deaths
),
tests: convertToIndianNumberingFormat(cardsDataFetched.tests),
critical: convertToIndianNumberingFormat(
cardsDataFetched.critical
),
});
const time = new Date(cardsDataFetched.updated);
const lastupdated = time.toLocaleTimeString();
setLastUpdated(`Last updated at ${lastupdated}`);
};
getData();
}, []);
return (
<ScrollView style={styles.main}>
<View style={{ marginTop: Constants.statusBarHeight }}>
<StatusBar style="auto" />
<Header text="COVID-19" />
<Text style={styles.lastUpdatedText}>{lastUpdated}</Text>
<View style={{ marginTop: marginAboveCard }}>
<Flexbox style={{ marginTop: 15 }}>
<Card
background="red"
title="Confirmed"
number={cardsData.confirmed}
/>
<Card
background="#3877F0"
title="Active"
number={cardsData.active}
/>
</Flexbox>
<Flexbox style={{ marginTop: marginAboveCard }}>
<Card
background="#47CC3C"
title="Recovered"
number={cardsData.recovered}
/>
<Card
background="#868686"
title="Deceased"
number={cardsData.deceased}
/>
</Flexbox>
<Flexbox style={{ marginTop: marginAboveCard }}>
<Card
background="#E85FEB"
title="Tests"
number={cardsData.tests}
/>
<Card
background="#5BF8B6"
title="Critical"
number={cardsData.critical}
/>
</Flexbox>
</View>
<View style={{ marginTop: 30 }}>
<Header text="TABLE STATISTICS" />
</View>
</View>
</ScrollView>
);
};
export default HomeScreen;
const styles = StyleSheet.create({
main: {
flex: 1,
backgroundColor: "#000232",
},
lastUpdatedText: {
color: "white",
textAlign: "center",
marginTop: 12,
fontSize: 15,
},
});
My app.jsx -
import * as React from "react";
import HomeScreen from "./components/screens/HomeScreen";
import VaccineScreen from "./components/screens/VaccineScreen";
import { NavigationContainer } from "#react-navigation/native";
import { createDrawerNavigator } from "#react-navigation/drawer";
const Drawer = createDrawerNavigator();
export default function App() {
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Vaccine" component={VaccineScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
}
If you look at the code of home screen properly, you would see a status style component which is set to auto. Even after removing it, the space is there. There are no errors in the console of any sort. This error started coming when I used react navigation drawer. Is there a way I can remove the space?
Did you try to move StatusBar as top most child like my sample below?
....
<View>
<StatusBar style="auto" />
<ScrollView style={styles.main}>
<View style={{ marginTop: Constants.statusBarHeight }}>
...
How you define the header title and the drawer icon?

Pass Function to Functional Component in React Native

I have an ItemForm component in a react-native expo app I would like to use for both Creates and Updates. It is a functional component which I am trying to pass a handler from a parent component to either create a new item or update an existing item. I am having trouble understanding how to pass the onPress handler down from the parent Class Component down to the functional child form Component.
Parent Component:
import React, { Component } from "react";
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
// You can import from local files
import ItemForm from './components/ItemForm';
// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';
export default class App extends Component {
handleSubmit = ({ name }) => {
alert(name);
// todo: get form data and create or update database
}
render() {
return (
<View style={styles.container}>
<Card>
<ItemForm onSubmit={() => this.handleSubmit}/>
</Card>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
ItemForm.js
import React, { useState } from "react";
import { View, Text, StyleSheet } from "react-native";
import { Button, Card, Icon, Input } from "react-native-elements";
export default function ItemForm({ onSubmit }) {
const [name, setName] = useState("");
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerText}>Add an Item</Text>
</View>
<Card
containerStyle={{
height: "80%",
margin: 0,
elevation: 1,
borderWidth: 0,
shadowOpacity: 0,
shadowRadius: 0,
}}
>
<Input
label="Item Name"
labelStyle={styles.itemText}
placeholder="Name Here"
onChangeText={(name) => setName(name)}
/>
<Button
icon={
<Icon
name="check-circle"
size={20}
style={{ paddingRight: 10 }}
onPress={onSubmit({name: name})}
/>
}
title="Save Item"
/>
</Card>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "column",
},
header: {
alignItems: "center",
paddingTop: 40,
},
headerText: {
fontSize: 32,
},
itemText: {
fontSize: 25,
}
});
This is currently throwing:
onSubmit is not a function. (In 'onSubmit({name: name})', 'onSubmit' is undefined)
I've tried various different syntaxes of passing or calling the function by reading the docs here but I run into a lot of errors like Please attach a method to this component and I can't seem to get past this?
onPress should be a function that calls onSubmit, not the return value of calling onSubmit (onPress={onSubmit({name: name})}).
onPress needs to be on the Button.
You need to pass down this.handleSubmit from the parent component, and not a function that returns this.handleSubmit (onSubmit={() => this.handleSubmit}):
So in App:
<ItemForm onSubmit={this.handleSubmit}/>
In ItemForm:
<Button
onPress={() => onSubmit({name: name})}
icon={
<Icon
name="check-circle"
size={20}
style={{ paddingRight: 10 }}
/>
}
title="Save Item"
/>
demo

Resources