React Native, Passing Navigation to Stateless Flatlist component - react-native

Currently I have:
return(
<View style={{flex: 1, paddingTop:20}}>
<FlatList
data={this.state.dataSource}
renderItem={(data) => <EventCard eventinfo = {data.item} navigation=
{this.props.navigation}/>}
keyExtractor={(item) => item.eventname}
/>
and
const EventCard = ({eventinfo, navigation}) => {
return (
<TouchableOpacity style={{backgroundColor: 'transparent'}} onPress= {()
=> navigation.navigate('CurrRes')}>
I dont understand why Navigation cant be evaluated in my Eventcard, and navigation doesnt work. Any help would be appreciated.
(yes withnavigation is imported in the first file and the project runs but crashes when one of the flatlist items is pressed)
The error i get is
undefined is not an object (evaluating 'navigation.navigate')

Here is an example I found to explain it better.
You need to access props differently than just a normal function with deconstruction.
const Child = (props) => {
return (
<div style={{backgroundColor: props.eyeColor}} />
)
}
https://medium.com/#PhilipAndrews/react-how-to-access-props-in-a-functional-component-6bd4200b9e0b

Nevermind, I had to export the actual list with navigation so each of its nested stateless components could navigate. Sorry!

Related

React native FlatList not rerendering when data prop changes to empty array

I have a FlatList with a data prop pulling from Redux
render() {
return (
<View>
<FlatList
data={this.props.arrayOfPlacesFromRedux}
renderItem={({item}) => {.......
Whenever I dispatch changes to arrayOfPlacesFromRedux(i.e. adding or removing children), the FlatList rerenders....UNLESS I remove all children from array (i.e. make length zero).When arrayOfPlacesFromRedux changes from a positive length to a length of zero, the FlatList does not rerender.....however all other types of changes to array do indeed cause FlatList to rerender
UPDATE 02/27
Below is my reducer used to update Redux arrayOfPlacesFromRedux
const reducer = (state = initialState, action) => {
switch (action.type) {
case UPDATE_PLACES_ARRAY:
return {...state, arrayOfPlaces: action.payload};
default:
return state;
}
};
In the situation noted above when FlatList does not rerender.....action.payload is an empty array
The question is missing some important piece of code.
React as well as Redux need arrays reference to change, meaning for a component to reRender on state change, the array references needs to change.
Live demo at https://snack.expo.dev/RrFFxfeWY
Here is the most interesting parts:
If you have a basic component as below:
const MyList = () => {
const [data, setData] = React.useState([
'#FF0000',
'#FF8000',
'#FFFF00',
]);
return (
<>
<Text>List poping is not working</Text>
<FlatList
data={data}
renderItem={({ item }) => (
<Pressable
onPress={() => {
data.pop(); // Does not work because we are not changing it's ref
}}
style={{ backgroundColor: item, padding: 8 }}>
<Text>{item}</Text>
</Pressable>
)}
/>
</>
);
};
The data need to have a new array reference as below. data2.filter(..) will return a new array, we are not changing the data2 base values, just creating a new array with one item less.
const MyList = () => {
const [data2, setData2] = React.useState([
'#00FFFF',
'#0080FF',
'#0000FF',
]);
return (
<>
<Text>List WORKING!</Text>
<FlatList
data={data2}
renderItem={({ item }) => (
<Pressable
onPress={() => {
setData2(data2.filter(dataItem => dataItem !== item)) // works
//setData2([]); // Also works
}}
style={{ backgroundColor: item, padding: 8 }}>
<Text>{item}</Text>
</Pressable>
)}
/>
</>
);
};
A library like Immer.js simplify the manipulation of states to mutate the object, and immer will created a new reference for you.
Oh no rookie mistake that wasted everyones time!!
I was implementing shouldComponentUpdate method that was stopping Flatlist rendering :(
Thanks for all for the answers
You may need to use ListEmptyComponent, which is a prop that comes with FlatList, src.
Honestly, I'm not sure why it does not re-render when you update your state, or why they added a specific function/prop to render when the array is empty, but it's clear from the docs that this is what's needed.
You can do something like this:
<SafeAreaView style={styles.container}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
extraData={selectedId}
--> ListEmptyComponent={() => <MyComponent />}
/>
</SafeAreaView>

Can't figure out why React Hook is invalid

I'm trying to use useContext hook in a React Native page I have, I'm using this hook in other components as well and they work just fine, but this one won't - can't figure out why.
This is my component:
const GoalCard = (goal: IGoal) => {
const {theme} = useContext(ThemeContext); <-- error is here
return (
<Animated.View style={[styles.card, {height: cardHeight, backgroundColor: "#ff5e"}]}>
<View>
.....
</View>
</Animated.View>
);
I'm using this component inside a FlatList which is in a separate file.
return (
<SafeAreaView style={[styles.pageContainer, {backgroundColor: theme.background}]}>
<FlatList
showsVerticalScrollIndicator={false}
style={{padding: 8}}
data={goals}
renderItem={({index}) => GoalCard(goals[index])}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
I get the error: Invalid hook call, but it does not seem like something is wrong here.
Saw few questions about this, but none worked, also, nothing seems wrong according to the Rules of Hooks page. Why this error happens here?
EDIT:
this is my ThemeProvider:
function ThemeProvider({ children }) {
const [dark, setDark] = React.useState(false);
const toggle = () => {
setDark(!dark);
}
const theme = dark ? themes.dark : themes.light;
return (
<ThemeContext.Provider value={{theme, dark, toggle}}>
{children}
</ThemeContext.Provider>
)
}
And this is my App.tsx:
<ThemeProvider>
<Provider store={store}>
<NavigationContainer>
.....
</NavigationContainer>
</Provider>
</ThemeProvider>

How should I use hooks in a nested component

I'm building an app with React Native, and I have a card component.
This card component needs few hooks, the problem is that the card components being rendered in FlatList in a HomeScreen component.
So the hierarchy is like this:
-Homescreen Component
---FlatList
----Card1
----Card2
----Card3
From what I understand, it is not possible to use React Hooks in my card component (Invalid hook call error). which means I can't use something like PanGestureHandler because I need to use hooks for that.
I couldn't find anything, but I'm sure this is a common problem and there is a solution for this.
What is the best way to deal with this?
This is my FlatList:
<FlatList
showsVerticalScrollIndicator={false}
style={{padding: 8}}
data={goals}
renderItem={({index}) => GoalCard(goals[index], theme, hook1, hook2)}
keyExtractor={item => item.id}
/>
This is my card:
const GoalCard = (goal: IGoal, theme: any, hook1: any, hook2: any) => { ... }
You can use nested hooks even inside flatlist make sure your Component name start with Capital latter and if possible share more info about error
after changing this use props.hook1 etc.
<FlatList
showsVerticalScrollIndicator={false}
style={{padding: 8}}
data={goals}
renderItem={({index}) => <GoalCard index={goals[index]} theme={theme} hook1={hook1} hook2={hook2})}
keyExtractor={item => item.id}
/>
you can pass hook down as prop, see the example.
const [value, setValue] = React.useState(1);
<FlatList renderItem={({item}) => <Card value={value} setValue={setValue}/>
const Card = ({value, setValue}) => {
return <Text onPress={() => setValue(value + 1)}>click me</Text>
}

How to use React Navigation with an element inside of a FlatList's Header?

I can navigate perfectly within the renderItem, but when i try within the component that I use in the header when I click it says "undefinded is not an object (evaluating '_this.props.navigation.navigate')
This is how Im trying to navigate within the component thats been passed to the FlatLists's header
<TouchableOpacity onPress={() => this.props.navigation.navigate('Profile', { data: info })}>
I passed through the navigation prop to the ListHeaderComponent, and it works, because the rendered element gave me some other problems
This is how i did it
return(
<SafeAreaView style={styles.container}>
<FlatList
ListHeaderComponent={
<>
<Header navigation={this.props.navigation}/>
</>
}
style={styles.scrollView}
numColumns='2'
columnWrapperStyle={{justifyContent: 'space-between'}}
data={ex}
renderItem={renderItem}
keyExtractor={(item, index) => index.toString()}
/>
</SafeAreaView>
);
Just a hunch, but my guess is the component is being passed for ListHeaderComponent instead of a rendered element with the navigation prop.
For example:
function Parent(props) {
// Here the FlatList will render the header as <MyHeader /> with no props
// If this.props.navigation is called in MyHeader it will be undefined
return (
<FlatList ListHeaderComponent={MyHeader} ... />
);
}
vs
function Parent(props) {
// here MyHeader is rendered as an element first, passing through
// the necessary navigation prop
const headerElement = (<MyHeader navigation={props.navigation} />);
return (
<FlatList ListHeaderComponent={headerElement} ... />
);
}

Flatlist inside a function in react native

How can I create an flatlist and add items to it inside a function not a class in react native?? all of the examples online are using classes and I need to use it inside a function !!
I found an example of a FlatList in the React Native docs that is using a functional component:
https://reactnative.dev/docs/flatlist
If you just want the code check out the same example on snack:
https://snack.expo.io/?session_id=snack-session-R6Nsz_Qm1&preview=true&platform=web&iframeId=uetjvvask3&supportedPlatforms=ios,android,web&name=flatlist-simple&description=Example%20usage&waitForData=true
I hope it helped :)
Same as with any other component, there's not much difference between using a FlatList inside a class vs a function. Only the state handling changes a little bit.
The code below will render all items, you'll be able to press on any of them to duplicate the item which should then show up at the bottom of the list.
export const FlatListScreen = props => {
const [items, setItems] = useState([1, 2, 3, 4, 5]);
function duplicateItem(toDuplicate) {
setItems(prev => [...prev, toDuplicate]);
}
return (
<FlatList
data={items}
renderItem={({ item }) => (
<TouchableWithoutFeedback onPress={() => duplicateItem(item)}>
<View>
<Text>
{item}
</Text>
</View>
</TouchableWithoutFeedback>
)}
/>
);
}