How should I use hooks in a nested component - react-native

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>
}

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>

how to use flatlist in react native with fetch api

I am using react native with laravel back end so i just want to load list from laravel so for that i code like that
constructor() {
super();
this.state = {
data:[
{
student_name: '',
class:'',
section:'',
},
],
}
//id is also in state and i get it's value from async storage
fetch('http://192.1.1.:8000/api/students/' + this.state.id, {
method: 'get',
})
.then((response) => response.json())
.then((responseJson) => {
if (responseJson.message === 'success') {
responseJson.data.map((userData) => {
this.setState({student_name: userData.student_name})
this.setState({class: userData.class})
this.setState({section: userData.section})
});
}
that's how i get record from laravel and update state in react native. but when i use flat list in react native it throw me that error
VirtualizedList: missing keys for items, make sure to specify a key or id property on each item or provide a custom keyExtractor.
My react native view is like that
<View>
<Text style={{fontSize:50}}>FlatList</Text>
<FlatList
data={this.state.data}
renderItem={({item})=><Text style={{fontSize:50}}>{item.student_name}</Text>}
/>
</View>
Can you try this?
keyExtractor={(item, index) => index.toString()}
You have to add a unique key (or id) prop for Text element in FlatList.
Supposing that your this.state.data items have an id, you could write something like:
<View>
<Text style={{fontSize:50}}>FlatList</Text>
<FlatList
data={this.state.data}
renderItem={({item})=><Text key={item.id} style={{fontSize:50}}>{item.student_name}</Text>}
/>
</View>
Alternatively, you could add keyExtractor to FlatList in this way:
<View>
<Text style={{fontSize:50}}>FlatList</Text>
<FlatList
data={this.state.data}
renderItem={({item})=><Text style={{fontSize:50}}>{item.student_name}</Text>}
keyExtractor={(item) => item.id}
/>
</View>
in Flatlist you have been need on key parameter . you must get that from list , like id parameter .
add this parameter to flatlist item
style={styles.flatListStyle}
data={this.state.bestSuggester}
key={item => item.Rank}
Its because you are missing keyExtractor
Replace your code with this:
<View>
<Text style={{fontSize:50}}>FlatList</Text>
<FlatList
data={this.state.data}
keyExtractor={(item, index) => String(index)}
renderItem={({item})=><Text style={{fontSize:50}}>{item.student_name}</Text>}
/>
</View>

ERROR - VirtualizedLists should never be nested inside plain ScrollViews with the same orientation

I'm working on a react-native app and I have to put a list of object in a Scrollview, so I use the FlatList component to do it. This is the piece of code that generates the error:
<ScrollView contentContainerStyle={style}>
Other components
<FlatList
style={style}
data={data}
scrollEnabled={false}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index}) => (somethings)}
/>
Other components
</ScrollView>
The complete error is: VirtualizedLists should never be nested inside plain ScrollViews with the same orientation because it can break windowing and other functionality - use another VirtualizedList-backed container instead.
Avoid using FlatList with the same orientation. Instead, restructure your code like this --
<ScrollView contentContainerStyle={style}>
Other components
{
data.map((item)=> <Somthing item={item}/>)
}
Other components
</ScrollView>
Flatlist has its own ScrollView you can scroll through the list using that so there is no need to put a flatlist into a ScrollView that is why its giving a warning, the both scrollview will clash and one of them (mostly the parent one) works.
The error is self explanatory and it should be in a developers best interest to avoid these kind of things even when it's just a false alarm.
Your particular situation could use the following solution:
<FlatList
data={data}
keyExtractor={(item, index) => `key-${index}`}
ListHeaderComponent={() => (
<SomeComponents>
...Some components those need to be on top of the list
</SomeComponents>
)}
ListFooterComponent={() => (
<SomeComponents>
...Some components those need to be below the list
</SomeComponents>
)}
renderItem={({ item, index}) => (somethings)}
/>
Another note, if you need more complex list that needs header and footer for the list itself, you can try SectionList.
Your component FlatList and ScrollView have the same orientation(vertical), so you need put your component inside a ScrollView with horizontal orientation like this:
<View>
<ScrollView nestedScrollEnabled={true} style={{ width: "100%" }} >
<View>
<ScrollView horizontal={true} style={{ width: "100%" }}>
<FlatList />
</ScrollView>
</View>
</ScrollView>
</View>
Solution 1: Use FlatList props ListHeaderComponent and create all of your page top section in that. Something like this:
This will not show any warning or error.
Solution 2:
Because only parent view will scroll (ScrollView) and not the child FlatList, so to get rid of the warning you can pass a prop scrollEnabled={false} to the FlatList.
If it doesn't go then import LogBox from react-native and write this in your component
useEffect(() => {
LogBox.ignoreLogs(["VirtualizedLists should never be nested"])
}, [])
hopefully, the warning will be removed.
Anyone want to solve this issue can use a custom VirtualizedScrollView like this:
import React from 'react';
import { FlatList } from 'react-native';
const VirtualizedScrollView = props => {
return (
<FlatList
{...props}
data={[]}
keyExtractor={(e, i) => 'dom' + i.toString()}
ListEmptyComponent={null}
renderItem={null}
ListHeaderComponent={() => (
<>{props.children}</>
)}
/>
);
};
export default VirtualizedScrollView;
Then if you use FlatList inside VirtualizedScrollView, it won't get the warning/error.
<VirtualizedScrollView>
<FlatList
/*--- your props ---*/
/>
</VirtualizedScrollView>
There is a npm package where I get this code, you can also use this package
Solution:
I have also encountered same problem with FlatList. Then the package below solved my problem.
'react-native-virtualized-view'
import { ScrollView } from 'react-native-virtualized-view'
if ScrollView is Vertical change Flatlist Horizontal
<ScrollView >
<FlatList
horizontal
data={lenders}
keyExtractor={(_, index) => index}
renderItem={(item) => {
return <Text>item</Text>
}}
/>
You can solve the 2 vertical ones(I'm assuming their side by side, separated with a segemented control?) by using the same flat list and switching out the data when it's switched. If they're just two vertical flat list's one after another use the SectionList.
For the horizontal one you can try putting the Horizontal FlatList in the ListHeaderComponent of the vertical FlatList and see what happens. It can be janky if you use a vertical FlatList in a vertical scroll view but maybe with two different axis it might be ok. The other option is two only show a few items in the horizontal scrollview and have a "Show More".
The last option is too re design/rethink the page so it's not doing so much. On mobile less is more and developers/designers like to get in the mindset of porting desktop thinking onto mobile. Might be worth a shot.
I used the SectionList approach to solve this & wanted to post a code example because I found the Section data required by React Native to be clear but also quite prescriptive.
renderList = ({empty, posts}: {empty: boolean, posts: Array<Object>}) => (
<SectionList
sections={[
{type: 'MAP', data: [{}]}, // Static sections.
{type: 'PROFILE', data: [{}]},
{type: 'POSTS', data: posts} // Dynamic section data replaces the FlatList.
]}
keyExtractor={(item, index) => index}
renderItem={({item, section}) => {
switch (section.type) {
// Different components for each section type.
case 'MAP':
return <MapView />;
case 'PROFILE':
return <Profile />;
case 'POSTS':
return <Post item={item} />;
default:
return null;
}
}}
ItemSeparatorComponent={() => <Separator />}
ListFooterComponent={() => <>{empty && <EmptyList />}</>}
/>
);
What's nice is that the content feels logically quite separate, so you can add sections easily or have different dynamic data sources.
(If you're building a form & want better keyboard handling, you could also try a KeyboardAwareSectionList from react-native-keyboard-aware-scroll-view.)
Flatlist has an integrated scrollview itself, so you can resolve this error by removing ScrollView Component, And let just the Fatlist component
Error ? you are trying to render a FlatList component inside a scrollview component, this is what is throwing the warning.
solution Render the components using Flatlist's ListHeaderComponent={} prop, i.e in your flatlist add the prop as follows
const FlatList_Header = () => {
return (
<View style={{
height: 45,
width: "100%",
backgroundColor: "#00B8D4",
justifyContent: 'center',
alignItems: 'center'
}}
>
<Text style={{ fontSize: 24, color: 'white' }}> Sample FlatList Header </Text>
</View>
);
}
<FlatList
data={BirdsName}
renderItem={({ item }) => <ItemRender name={item.name} />}
keyExtractor={item => item.id}
ItemSeparatorComponent={ItemDivider}
**ListHeaderComponent={FlatList_Header}**
ListHeaderComponentStyle={{ borderBottomColor: 'red', borderBottomWidth: 2 }}
/>
Note the use of the ListHeaderComponent in the code above, that should supress the warning.
Use flatList like this ListHeaderComponent and ListFooterComponent:
<FlatList ListHeaderComponent={
<ScrollView
style={styles.yourstyle}
showsVerticalScrollIndicator={false}
>
<View style={styles.yourstyle}>
</View>
</ScrollView>
}
data={this.state.images}
renderItem={({ item, index }) => {
return (
<View
style={styles.yourstyle}
>
<Image
source={{
uri: item,
}}
style={styles.yourstyle}
resizeMode={"contain"}
/>
<Text
numberOfLines={2}
ellipsizeMode="tail"
style={styles.yourstyle}
>
{item.name}
</Text>
</View>
);
}}
keyExtractor={({ name }, index) => index.toString()}
ListFooterComponent={
<View style={styles.yourstyle}></View>
}
/>
In my case it was happening due to nesting of ScrollView.
Try replacing some of the ScrollView from children components with React.Fragment.
The solution is very simple, please do not put the Flatlist component in the ScrollView.
They both have the same functionality but Flatlist has advantages and is more stable to use.

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>
)}
/>
);
}

React Native, Passing Navigation to Stateless Flatlist component

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!