I'm making a notes app in React Native and trying to make it so I can click on a note in a FlatList to edit it. I'm using react-router-native for this. I get an Error when clicking on any FlatList item. I know that this error has been asked on stack overflow before but the answers are all for class components, whereas I'm using functional components.
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
import { FlatList, Pressable, StyleSheet, View } from "react-native"
import { useNavigate } from "react-router-native"
import theme from "../theme"
import Text from "./Text"
const styles = StyleSheet.create({
separator: {
height: 10,
backgroundColor: theme.colors.background,
},
item: {
padding: 8,
backgroundColor: "white",
},
})
const ItemSeparator = () => <View style={styles.separator} />
const renderItem = ({ item }) => (
<View style={styles.item}>
<Pressable onPress={() => useNavigate(`/${item.id}`)}>
<Text fontWeight="bold" fontSize="subheading">
{item.title}
</Text>
<Text>{item.body}</Text>
</Pressable>
</View>
)
const NoteList = ({ notes }) => {
return (
<FlatList
data={notes}
ItemSeparatorComponent={ItemSeparator}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
)
}
useNavigate is a React hook and can only be called by a React function component or other custom React hook. It cannot be called in nested functions/callbacks.
Move the useNavigate hook call to the NoteList component and refactor the renderItem callback to curry a passed navigate function.
const ItemSeparator = () => <View style={styles.separator} />;
const renderItem = (navigate) => ({ item }) => (
<View style={styles.item}>
<Pressable onPress={() => navigate(`/${item.id}`)}>
<Text fontWeight="bold" fontSize="subheading">
{item.title}
</Text>
<Text>{item.body}</Text>
</Pressable>
</View>
);
const NoteList = ({ notes }) => {
const navigate = useNavigate(); // <-- hook called in React function
return (
<FlatList
data={notes}
ItemSeparatorComponent={ItemSeparator}
renderItem={renderItem(navigate)} // <-- pass navigate
keyExtractor={(item) => item.id}
/>
);
};
Alternatively you could move the renderItem function declaration into the NoteList component so the navigate function is just closed over in callback scope.
const ItemSeparator = () => <View style={styles.separator} />;
const NoteList = ({ notes }) => {
const navigate = useNavigate();
const renderItem = ({ item }) => (
<View style={styles.item}>
<Pressable onPress={() => navigate(`/${item.id}`)}>
<Text fontWeight="bold" fontSize="subheading">
{item.title}
</Text>
<Text>{item.body}</Text>
</Pressable>
</View>
);
return (
<FlatList
data={notes}
ItemSeparatorComponent={ItemSeparator}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
);
};
Related
I have a screen with the following:
function Intereset ({ navigation }) {
function ReturnMyFunction () {
if (!var.length) {
return ( <NoILikes /> )
} else {
return (
<FlatList
data={Ilike}
keyExtractor={item => item._id}
ItemSeparatorComponent={() => <Divider />}
renderItem={UserRow}
/>
)
}
}
return ( <ReturnILikeOrNoILike /> )
}
export default Interest
Here is my UserRow component below:
const UserRow = ({ item, navigation }) => (
<TouchableOpacity onPress={() => navigation.navigate("ProfileDetailScreenSingle", { userID: item.likeTo })}>
<View style={styles.row}>
<Image style={styles.avatar}
resizeMode={"cover"}
source={{ uri: item.likeToProfileImage }}
/>
<View style={styles.textContainer}>
<Text style={styles.name}>{item.likeToName}, <Text>{item.likeToAge}</Text></Text>
</View>
<Text style={styles.viewProfileText}>View Profile</Text>
<AntDesign name="right" size={14} color="black" />
</View>
</TouchableOpacity>
)
When I click on the UserRow to navigate I get the following issue.
I'm using useNavigation to redirect screen in stack:
import { useNavigation } from "#react-navigation/native";
const UserRow = ({ item }) => {
const navigation = useNavigation()
const onGoToProfileDetailScreenSingle = () => navigation.navigate("ProfileDetailScreenSingle", { userID: item.likeTo })
return (
<TouchableOpacity onPress={onGoToProfileDetailScreenSingle}>
...
</TouchableOpacity>
);
}
const renderItem = ({ item }) => <UserRow item={item} />
return (
<FlatList
data={Ilike}
renderItem={renderItem}
...
/>
)
Use onPress handler instead and perform navigation on the screen.
const UserRow = ({ item, onPress }) => (
<TouchableOpacity onPress={onPress}>
...
</TouchableOpacity>
);
const renderItem = ({ item }) => {
return (
<UserRow
item={item}
onPress={() => navigation.navigate(...)}
/>
);
};
<FlatList
data={Ilike}
renderItem={renderItem}
...
/>
hope someone would help me with this problem. I have create my own buttons and when I`m calling them as a component in a screen onPress navigation is not working, only in the Button Component from react native. This is my code:
Custom Button:
export const MediumButton = ({ title }) => {
return (
<TouchableOpacity style={styles.mediumButton}>
<Text style={styles.buttonText}>{title}</Text>
</TouchableOpacity>
);
};
And this is the code of screen that I`m calling the MediumButton:
import { MediumButton } from "../../components/Button/Button";
export default function Home({ navigation }) {
return (
<View style={global.container}>
<View style={styles.container}>
<Text style={styles.titleText}>Miresevini ne Hajde App</Text>
<MediumButton
title="Kycu"
onPress={() => navigation.navigate("Register")}
/>
<MediumButton
title="Regjistrohu"
onPress={() => navigation.navigate("Login")}
/>
</View>
</View>
);
}
You have to use the onPress prop that you are passing like below
export const MediumButton = ({ title,onPress }) => {
return (
<TouchableOpacity style={styles.mediumButton} onPress={onPress}>
<Text style={styles.buttonText}>{title}</Text>
</TouchableOpacity>
);
};
Try to pass the onPress handler to TouchableOpacity:
export const MediumButton = ({ title, onPress }) => {
return (
<TouchableOpacity style={styles.mediumButton} onPress={onPress}>
<Text style={styles.buttonText}>{title}</Text>
</TouchableOpacity>
);
};
I am still trying to finish my first React Native app. There is another thing for which I cannot find a solution.
My application has different Screens (HomeScreen, NewsScreen, etc). Each of these screens uses the component Screen:
const Screen = ({props, children}) => {
console.log("Screen props: " + props);
return (
<>
<OfflineNotice />
<Header {...props} />
<SafeAreaView style={styles.screen}>
<View style={styles.container}>{ children }</View>
</SafeAreaView>
</>
);
}
export default Screen;
Each Screen component has another Header component (there is a dropdown, by choosing another option it will change the displayed information on the screen)
Here is the Header component:
export default function Header (props) {
console.log("Header props: " + props.loadPredictions);
const {sport, setSport} = useContext(AppContext);
const logo = config[sport].logo;
return (
<View style={styles.container}>
<View style={styles.link}>
<TouchableOpacity
onPress={() => props.navigate("Settings")}
>
<MaterialCommunityIcons name="tune" size={32} />
</TouchableOpacity>
</View>
<View style={styles.logo}>
<Image source={logo} style={styles.image} />
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Menu
ref={this.setMenuRef}
button={<MaterialCommunityIcons name="chevron-down" size={24} onPress={this.showMenu} />}
>
<MenuItem onPress={() => {changeSport('soccer', setSport, props)}}>{i18n.t('predictions.p_1')}</MenuItem>
<MenuItem onPress={() => {changeSport('basketball', setSport, props)}}>{i18n.t('predictions.p_3')}</MenuItem>
<MenuItem onPress={() => {changeSport('volleyball', setSport, props)}}>{i18n.t('predictions.p_4')}</MenuItem>
<MenuItem onPress={() => {changeSport('hockey', setSport, props)}}>{i18n.t('predictions.p_5')}</MenuItem>
<MenuItem onPress={() => {changeSport('tennis', setSport, props)}}>{i18n.t('predictions.p_6')}</MenuItem>
</Menu>
</View>
</View>
</View>
);
};
And here is my HomeScreen:
export default function HomeScreen({ navigation }) {
...
useEffect( () => {
loadPredictions(params);
}, []);
const loadPredictions = async (params) => {
...
}
return (
<Screen navi={navigation} loadPredictions={loadPredictions}>
<View style={styles.activity}>
<ActivityIndicator animating={loading} size="large" />
</View>
<View style={styles.main}>
...
</View>
</Screen>
);
}
On the HomeScreen I am passing through the Screen component two props: navi and loadPredictions. Unfortunately, I cannot access them. I am getting the error undefined.
What am I doing wrong?
Simply because you made a distructuring the props in Screen Component, thus if you console log the props you will find it undefined, the best approach that you do this:
const Screen = ({children, ...restProps}) => {
console.log("Screen props: " + restProps);
return (
<>
<OfflineNotice />
<Header {...restProps} />
<SafeAreaView style={styles.screen}>
<View style={styles.container}>{ children }</View>
</SafeAreaView>
</>
);
}
export default Screen;
In this way we will be able to access to children prop and you pass the rest of the Screen Props to what Component you want.
I hope this answer can help you out.
change this
const Screen = ({props, children})
to
const Screen = (props)
and access children props.children
I'm sure this is something that is pretty common but can't seem to get it figured out. Each user has a "books" array in Firestore, and this is what I want to be returned in the Flatlist, where am I going wrong? The first day with firebase so I'm pretty sure its something basic. Thanks :)
<FlatList
data={() => { db.collection('users').doc(userEmail).data() }}
numColumns={2}
ListEmptyComponent={
<View style={styles.flatListEmpty}>
<Text style={{ fontWeight: 'bold' }}>Add Books Below</Text>
</View>
}
renderItem={({ item }) => (
<View style={styles.flatListStyle}>
<Text>book</Text>
</View>
)}
/>
then this is the database:
The problem is that query to firebase is asynchronous action, so you need to resolve that action then store the result to the state of component and assign that state to flatlist. Example:
const Component = () => {
const [data, setData] = useState();
const getData = async () => {
const snapshot = await db.collection('users').doc(userEmail).get()
setData(snapshot.data())
}
useEffect(() => {
getData()
}, [])
return (
<FlatList
data={data.books}
numColumns={2}
ListEmptyComponent={
<View style={styles.flatListEmpty}>
<Text style={{ fontWeight: 'bold' }}>Add Books Below</Text>
</View>
}
renderItem={({ item }) => (
<View style={styles.flatListStyle}>
<Text>book</Text>
</View>
)}
/>
)
}
I am using flat list to display data which is coming from unsplash api. But here it keeps on complaining to saying this
Invariant Violation: Text strings must be rendered within a component
I am not even using any text component. I have no idea what is wrong here.
App.js
export default function App() {
const [loading, setLoading] = useState(true);
const [image, setImage] = useState([]);
const {height, width} = Dimensions.get('window');
const URL = `https://api.unsplash.com/photos/random?count=30&client_id=${ACCESS_KEY}`;
useEffect(() => {
loadWallpapers();
}, [])
const loadWallpapers =() => {
axios.get(URL)
.then((res) => {
setImage(res.data);
setLoading(false);
}).catch((err) => {
console.log(err)
}).finally(() => {
console.log('request completed')
})
}
const renderItem = (image) => {
console.log('renderItem', image);
return (
<View style={{height, width}}>
<Image
style={{flex: 1, height: null, width: null}}
source={{uri : image.urls.regular}}/>
</View>
)
}
return loading ? (
<View style={{flex: 1, backgroundColor: 'black', justifyContent: 'center',alignItems: 'center'}}>
<ActivityIndicator size={'large'} color="grey"/>
</View>
): (
<SafeAreaView style={{flex: 1, backgroundColor: 'black'}}>
<FlatList
horizontal
pagingEnabled
data={image}
renderItem={({ item }) => renderItem(item)} />}
/>
</SafeAreaView>
)
}
I thing data of Flatlist is null, try
<FlatList
horizontal
pagingEnabled
data = {image ? image : []}
renderItem={({ item }) => renderItem(item)} />}
/>
I needed to do something like this to make it work.
const renderItem = ({ item }) => { <---- I have destructured item here
console.log(item)
return (
<View style={{ flex: 1 }}>
</View>
);
};
<FlatList
scrollEnabled={!focused}
horizontal
pagingEnabled
data={image}
renderItem={renderItem}
/>