Two children with the same key in React Native with Native Base - javascript

How I can fix the following error: Warning: Encountered two children with the same key, [object Object]. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
That is my List:
<List style={custom.PartList}>
<FlatList extraData={this.state} data={this.state.data} keyExtractor={this._keyExtractor.bind(this)} renderItem={this._renderItem.bind(this)} />
</List>
That is my List Item:
/* Render Item - Render One Row - Item - (Tool) */
_renderItem({ item }) {
const custom = styles(this.props);
return (
<View style={custom.PartView}>
<ListItem style={custom.PartListItem} onPress={() => this._handleRead(item.tool_id, item.tool_name, item.tool_description, item.tool_count, item.tool_availability)}>
<Image style={custom.PartImage} source={require('#app/assets/images/tools.png')}/>
<Text style={custom.PartName}>{item.tool_name}</Text>
</ListItem>
</View>
);
}
/* /Render Item - Render One Row - Item - (Tool)/ */
And that is my keyExtractor method:
/* Key Extractor Method - For Index Tools */
_keyExtractor(index) {
return index.toString();
}
/* /Key Extractor Method - For Index Tools/ */

For FlatList Code
<List style={custom.PartList}>
<FlatList extraData={this.state} data={this.state.data}
keyExtractor={(item, index) => index.toString()}
renderItem={this._renderItem.bind(this)} />
</List>

You're binding this to keyExtractor function, so it'll provide this object as a first parameter (where you refer it as index). Therefore the return value will always be the string presentation of Object (= [Object object])
The simple solution is just to declare keyExtractor={this.keyExtractor} without any binding.

For me it worked adding exactly like this:
<FlatList
data={mySkills}
keyExtractor={this.keyExtractor}
renderItem={({item, index}) => <SkillCard key={index} skill={item} />}
/>

Related

Encountered two children with the same key REACT NATIVE

enter image description here
New in react here. Coding without using div, however is a constant issue the duplicated array and I don't know how to fix it.
return (
<SafeAreaView style={styles.container}>
<Screen>
<FlatList
data={messages}
keyExtractor={(message) => message.id.toString}
renderItem={({item})=>
<ListItem
title={item.title}
subTitle={item.description}
image={item.image}
onPress={()=> console.log("Message selected", item)}
renderRightActions={() => (<ListItemDeleteAction onPress={()=> handleDelete (item)}/>)}
/>}
ItemSeparatorComponent={ListItemSeparator}/>
</Screen>
</SafeAreaView>
First of all the toString is a function not a property so it will be with undefined, so all the items will have key with undefined and this is the cause of the error.
It should be
keyExtractor={(message) => message.id.toString()}
Also, make sure that each message in messages array with unique id.

Compose components to variable in reactjs

Is it possible to compose more different parts of component to variable?
documentation
const App = () => {
let element;
element = <View>
<Text>text</Text>
</View> // --> OK
element = element + <View>
<Text>text2</Text>
</View> // --> TypeError: Cannot read properties of undefined (reading 'height')
element.push(<View>
<Text>text3</Text>
</View>); // --> TypeError: element.push is not a function
return <>
{element}
</>
}
export default App;
I use reactjs 17.0.2, typescript and "#react-pdf/renderer": "2.3.0".
Update
Based on your question here, this should work:
<Document>
<Page size="A4" orientation="landscape">
{/* -- Table LEFT: -- */}
<View>
{/* -- Table Head: -- */}
<View>
<Text>Index</Text>
<Text>Brand</Text>
<Text>Type</Text>
</View>
{/* -- Table Body: -- */}
{data?.cars?.length &&
data.cars.map(({ id, brand, type }, index) => (
<View key={`${id}-left`}>
<Text>{index + 1}</Text>
<Text>{brand || ''}</Text>
<Text>{type || ''}</Text>
</View>
))}
</View>
</Page>
<Page size="A4" orientation="landscape">
{/* -- Table RIGHT: -- */}
<View>
{/* -- Table Head: -- */}
<View>
<Text>Color</Text>
<Text>Fuel</Text>
</View>
{/* -- Table Body: -- */}
{data?.cars?.length &&
data.cars.map(({ id, color, fuel }) => (
<View key={`${id}-right`}>
<Text>{color || ''}</Text>
<Text>{fuel || ''}</Text>
</View>
))}
</View>
</Page>
</Document>
The issue seems to be with how you're handling arrays, not with rending React elements.
If you want to access the properties of the object in an array element, you can destructure the element, so instead of
data.cars.map((car, index) => (<Text>{car.color}</Text>))
you can do
data.cars.map(({id, brand, type, color, fuel}, index) => (<Text>{color}</Text>));
If you're not performing any operations on the array elements, you can use an implicit return instead of an explicit return:
// explicit return
data.cars.map(({id, brand, type, color, fuel}, index) => {
// do something else here
return (
<Text>{color}</Text>
)
});
// implicit return
data.cars.map(({id, brand, type, color, fuel}, index) => (<Text>{color}</Text>));
Also, when you're rending known text values in React, you don't need to wrap it in curly braces ({}), you can just render the text directly.
Instead of
<Text>{'color'}</Text>
you can just put
<Text>color</Text>
unless it's required by whatever library you're using. I'm not familiar with #react-pdf/renderer.
One more thing to keep in mind is that the key for list items in React should be something stable. Using array indices as keys is discouraged (see React docs).
Original answer
If you want to render an element this way, you could do something like this:
const App = () => {
let element = [];
// Each child in a list needs a unique "key" prop
element.push(<View key={someUniqueKey}>
<Text>text</Text>
</View>)
element.push(<View key={someOtherUniqueKey}>
<Text>text2</Text>
</View>)
element.push(<View key={oneMoreUniqueKey}>
<Text>text3</Text>
</View>);
return <>
{element}
</>
}
export default App;
Personally, I haven't seen anyone render components like this.
The strategy you are looking for is called conditional rendering, and there are different ways to do this depending on the situation.
For example, if you're trying to dynamically render data from an API response, you could do something like this:
const App = () => {
const { data } = fetchDataFromAPI();
return (
<>
<View>
<Text>text</Text>
</View>
{data?.text2 && (
<View>
<Text>{data.text2}</Text>
</View>
)}
{data?.text3 && (
<View>
<Text>{data.text3}</Text>
</View>
)}
</>
);
};
export default App;
You can check out the React docs for conditional rendering and rendering lists.
(Note: The above links are for the beta docs. If you prefer the classic(?) docs: conditional rendering and lists)

react native SectionList component scrolling does not work if I use renderSectionHeader props before renderItem

I'm new to React Native and found this strange behaviour that react native SectionList component scrolling does not work if I use renderSectionHeader props before renderItem
Vertical scrolling Not Working
<SectionList
sections={DATA}
renderSectionHeader={({ section }) => (
<View style={styles.item}>
<Text style={styles.text}>{section.title}</Text>
</View>
)}
renderItem={({ item }) => (
<Text style={styles.text}>{item}</Text>
)}
/>
Vertical scrolling Working
<SectionList
sections={DATA}
renderItem={({ item }) => (
<Text style={styles.text}>{item}</Text>
)}
renderSectionHeader={({ section }) => (
<View style={styles.item}>
<Text style={styles.text}>{section.title}</Text>
</View>
)}
/>
Is this a bug as header is displayed first and later its children
I've compared your example with the one listed on the React Native docs. The difference is having the keyExtractor prop.
By taking out this keyExtractor prop, the scrolling behaviour of the component doesn't function at all, irregardless of the positioning of the renderSectionHeader and renderItem props.
Can you try adding the keyExtractor prop below and see if both prop arrangements affect the scrolling behaviour?
keyExtractor={(item, index) => item + index}

React-Native another VirtualizedList-backed container

After upgrading to react-native 0.61 i get a lot of warnings like that:
VirtualizedLists should never be nested inside plain ScrollViews with the same orientation - use another VirtualizedList-backed container instead.
What is the other VirtualizedList-backed container that i should use, and why is it now advised not to use like that?
If someone's still looking for a suggestion to the problem that #Ponleu and #David Schilling have described here (regarding content that goes above the FlatList), then this is the approach I took:
<SafeAreaView style={{flex: 1}}>
<FlatList
data={data}
ListHeaderComponent={ContentThatGoesAboveTheFlatList}
ListFooterComponent={ContentThatGoesBelowTheFlatList} />
</SafeAreaView>
You can read more about this here: https://facebook.github.io/react-native/docs/flatlist#listheadercomponent
Hopefully it helps someone. :)
Just in case this helps someone, this is how I fixed the error in my case.
I had a FlatList nested inside a ScrollView:
render() {
return (
<ScrollView>
<Text>{'My Title'}</Text>
<FlatList
data={this.state.myData}
renderItem={({ item }) => {
return <p>{item.name}</p>;
}}
/>
{this.state.loading && <Text>{'Loading...'}</Text>}
</ScrollView>
);
}
and I got rid of the ScrollView by using the FlatList to render everything I needed, which got rid of the warning:
render() {
const getHeader = () => {
return <Text>{'My Title'}</Text>;
};
const getFooter = () => {
if (this.state.loading) {
return null;
}
return <Text>{'Loading...'}</Text>;
};
return (
<FlatList
data={this.state.myData}
renderItem={({ item }) => {
return <p>{item.name}</p>;
}}
ListHeaderComponent={getHeader}
ListFooterComponent={getFooter}
/>
);
}
The best way is to disable that warning because sometimes Flatlist need to be in ScrollView.
UPDATE RN V0.63 ABOVE
YellowBox is now changed and replace with LogBox
FUNCTIONAL
import React, { useEffect } from 'react';
import { LogBox } from 'react-native';
useEffect(() => {
LogBox.ignoreLogs(['VirtualizedLists should never be nested']);
}, [])
CLASS BASED
import React from 'react';
import { LogBox } from 'react-native';
componentDidMount() {
LogBox.ignoreLogs(['VirtualizedLists should never be nested']);
}
UPDATE RN V0.63 BELOW
FUNCTIONAL
import React, { useEffect } from 'react';
import { YellowBox } from 'react-native';
useEffect(() => {
YellowBox.ignoreWarnings(['VirtualizedLists should never be nested']);
}, [])
CLASS BASED
import React from 'react';
import { YellowBox } from 'react-native';
componentDidMount() {
YellowBox.ignoreWarnings(['VirtualizedLists should never be nested']);
}
Data
// dummy data array
const data = [
{id: 1, name: 'Tom'},
{id: 2, name: 'Jerry'},
]
Solution #1
You can make a custom component for that like this
const VirtualizedList = ({children}) => {
return (
<FlatList
data={[]}
keyExtractor={() => "key"}
renderItem={null}
ListHeaderComponent={
<>{children}</>
}
/>
)
}
then use this VirtualizedList as parent component:
...
return (
<VirtualizedList>
<FlatList
data={data}
keyExtractor={(item, index) => item.id + index.toString()}
renderItem={_renderItem}
/>
<AnyComponent/>
</VirtualizedList>
)
Solution #2
If you use FlatList inside the ScrollView it gives warning which is annoying, so you can use array's map property, like this -
NOTE: It is not recommended way to show list. If you have small amount of that then you can use it that's totally fine, but if you want to show a list which get data from api and have lot's of data then you can go with other solutions. if you use map with large data then it affect your app performance
<ScrollView>
{data.map((item, index) => (
<View key={index}>
<Text>{item.name}</Text>
</View>
))}
</ScrollView>
Solution #3
if you make your FlatList horizontal (as per your need) then also warning will disappear
<ScrollView>
<FlatList
data={data}
keyExtractor={(item, index) => item.id + index.toString()}
horizontal={true}
/>
</ScrollView>
Solution #4
you can add header and footer component
In ListHeaderComponent and ListFooterComponent you can add any component so you don't need parent ScrollView
<FlatList
data={data}
keyExtractor={(item, index) => item.id + index.toString()}
ListHeaderComponent={headerComponent}
ListFooterComponent={footerComponent}
ListEmptyComponent={emptyComponent}
ItemSeparatorComponent={separator}
/>
// List components
const headerComponent = () => (
<View>
<Header/>
<Any/>
</View>
)
const footerComponent = () => (
<View>
<Footer/>
<Any/>
</View>
)
const emptyComponent = () => (
<View>
<EmptyView/>
<Any/>
</View>
)
const separator = () => (
<View style={{height: 0.8, width: '100%', backgroundColor: '#fff'}} />
)
The warning appears because ScrollView and FlatList share the same logic, if FlatList run inside ScrollView, it's duplicated
By the way SafeAreaView doesn't work for me, the only way to solve is
<ScrollView>
{data.map((item, index) => {
...your code
}}
</ScrollView>
The error disappears
Looking at the examples in docs I've changed container from:
<ScrollView>
<FlatList ... />
</ScrollView>
to:
<SafeAreaView style={{flex: 1}}>
<FlatList ... />
</SafeAreaView>
and all those warnings disappeared.
In my case, I needed to have FlatLists nested in a ScrollView because I am using react-native-draggable-flatlist to move ingredients and steps around in a recipe.
If we read the warning properly, it says that we should use another VirtualizedList-backed container to nest our child FlatList in. What I did is:
/* outside the component */
const emptyArry = []
/* render */
<FlatList
scrollEnabled={false}
horizontal
data={emptyArray}
ListEmptyComponent=(<DraggableList />)
/>
No more warning, and I think this is the pattern recommended by the warning.
<ScrollView horizontal={false} style={{width: '100%', height: '100%'}}>
<ScrollView horizontal={true} style={{width: '100%', height: '100%'}}>
<FlatList ... />
</ScrollView>
</ScrollView>
Below code works perfectly for me to disable annoying error:
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.
React Native 0.68.2
<ScrollView horizontal={false} style={{flex: 1}}>
<ScrollView
horizontal={true}
contentContainerStyle={{width: '100%', height: '100%'}}>
<FlatList ... />
</ScrollView>
</ScrollView>
I tried some ways to solve this, including ListHeaderComponent or ListFooterComponent, but it all didn't fit for me.
layout I wanted to achieve is like this, and I wanted to get scrolled in once.
<ScrollView>
<View>I'm the first view</View>
<View>I'm the second view</View>
<MyFlatList />
</ScrollView>
First I want to say thanks to this issue and comments, which gave me bunch of ideas.
I was thinking of ListHeaderComponent places above the Flatlist, but since my Flatlist's direction was column, the header I wanted to place went on the left of the Flatlist :(
Then I had to try on VirtualizedList-backed thing. I just tried to pack all components in VirtualizedList, where renderItems gives index and there I could pass components conditionally to renderItems.
I could have worked this with Flatlist, but I haven't tried yet.
Finally it looks like this.
<View>
<Virtualizedlist
data={[]}
initialNumToRender={1}
renderItem={(props)=>{
props.index===0 ? (1st view here) : props.index===1 ? (2nd view here) : (my flatlist)
}}
keyExtractor={item => item.key}
getItemCount={2}
getItem={ (data, index) => {
return {
id: Math.random().toString(12).substring(0),
}
}}
/>
</View>
(inside which lazyly renders↓)
<View>I'm the first view</View>
<View>I'm the second view</View>
<MyFlatList />
and of course able to scroll the whole screen.
As #Brahim stated above, setting the horizontal property to true seem to resolve the issues for a FlatList embedded in a ScrollView.
So I faced the same problem while using a picker-based component inside <ScrollView> and the one thing that helped me solve the problem was adding
keyboardShouldPersistTaps={true} inside the <ScrollView> as a prop.
This is my code snippet.
<ScrollView keyboardShouldPersistTaps={true}>
<SelectionDD studentstatus={studentStatus}/>
<SearchableDD collegeNames={collegeNames} placeholder='University'/>
</ScrollView>
I have two Flatlist; each of them has many Item also has a feature to collapse and expand.
Because of that, I can't use SafeAreaView.
I saw another solution and found a new way.
I define one Flatlist in the core component ( without Scrollview) and render each Flatlist with a map function inside ListHeaderComponent and ListFooterComponent.
<View style={{flex: 1}}>
<FlatList
style={{backgroundColor: 'white'}}
refreshing={loading}
onRefresh={() => sample()}
ListHeaderComponent = {
<View>
{collapse/expandComponent}
{this.state.sample1&& content1.map((item, index) => this.renderList1(item,index))}
</View>
}
ListFooterComponent = {
<View>
{collapse/expandComponent}
{this.state.sample2 && content2.map((item, index) => this.renderlist2(item,index))}
</View>
}
/>
</View>
In my opinion i can use map instead of FlatList. But in my case i wan't to show large list. Not using FlatList may cause performance issue. so i used this to suppress warning https://github.com/GeekyAnts/NativeBase/issues/2947#issuecomment-549210509
Without hiding YellowBox you still can implement scroollable view inside scrollable view. You can use this library. It replace the default Scrollview from React Native.
This may help someone down the line, be sure you to check how your components are nested. Removing the ScrollView from the top component fixed the issue.
I ran into this issue because I had two components nested like this essentially:
Component 1
<ScrollView>
<OtherStuff />
<ListComponent />
</ScrollView>
My second component 'ListComponent' had a FlatList already wrapped with SafeAreaView.
<SafeAreaView style={styles.container}>
<FlatList
data={todoData}
renderItem={renderItem}
ItemSeparatorComponent={() => <View style={styles.separator} />}
keyExtractor={item => item.id.toString()}
/>
</SafeAreaView>
In the end I replaced the ScrollView from the first component with a View instead.
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>
}
/>
If you use ScrollView and FlatList together you'll get inconsistent scroll behaviour.
So just remove ScrollView and use FlatList in a View.
<View flex={1}>
<FlatList
data={list}
renderItem={({ item }) => this.renderItem(item) }
keyExtractor={item => item.id}
/>
</View>
import React from 'react';
import { FlatList, ScrollViewProps } from 'react-native';
export const ScrollView: React.FC<ScrollViewProps> = props => {
return (
<FlatList
{...props}
data={[]}
renderItem={() => null}
ListHeaderComponent={() => (
<React.Fragment>{props.children}</React.Fragment>
)}
ListEmptyComponent={null}
keyExtractor={() => 'blank'}
/>
);
};
This will essentially work exactly like a ScrollView except without this error.
I was having this issue using a scrollview as parent view, and nesting a SelectBox from react-native-multi-selectbox package. I was able to solve this by adding listOptionProps={{nestedScrollEnabled: true}} like this:
<ScrollView>
<SelectBox
label="Select single"
options={serverData}
listOptionProps={{nestedScrollEnabled: true}}
value={input.elementSelected}
onChange={event =>
inputHandlerLang('elementSelected', event, key)
}
hideInputFilter={false}
/>
</ScrollView>
the error still present but scrolling within SelectBox works as well as within the parent scrollview. I also do have to suppress the error with LogBox. I don't know if there are any drawbacks to this but I'll try to test this more.
Update 1: this used to work in v0.68.2, but since I updated to patch v0.68.5, the warning became an error.
You have to remove ScrollView and enable scroll from FlatList using the property scrollEnabled={true}, you can place the other views inside ListHeaderComponent and ListFooterComponent
<View flex={1}>
<FlatList
data={data}
scrollEnabled={true}
showsVerticalScrollIndicator={false}
renderItem={({ item }) => (
<Text>{item.label}</Text>
)}
keyExtractor={item => item.id}
ListHeaderComponent={() => (
<Text>Title</Text>
)}
ListFooterComponent={() => (
<Text>Footer</Text>
)}
/>
</View>
Actually as I know, using nested VirtualizedLists, caused always performance issues, just the warning to that issue is new. I tried everything I found on the internet, non of them helped. I use now ScrollView or when you just have a normall View with maxHeight then you will be able to scroll if the content-height is bigger then the maxHeight of you View.
So:
<ScrollView>
{items.map((item, index) =>
<YourComponent key={index} item={item} />
)}
</ScrollView>
Or just:
<View style={{maxHeight: MAX_HEIGHT}}>
{items.map((item, index) =>
<YourComponent key={index} item={item} />
)}
</View>
This error disappeared because of using FlatList inside ScrollView. You can write like the following code.
<SafeAreaView style={styles.container}>
<View style={styles.container}>
<View>
<Header />
</View>
{(list.length == 0) &&
<View style={{flex:1, margin: 15}}>
<Text style={{textAlign: 'center'}}>No peripherals</Text>
</View>
}
<FlatList
data={list}
renderItem={({ item }) => this.renderItem(item) }
keyExtractor={item => item.id}
/>
</View>
</SafeAreaView>
You can add horizontal=True and contentContainerStyle={{ width: '100%' }} to the ScrollView parent.
<ScrollView
style={styles.collaborators}
contentContainerStyle={{ width: '100%' }} <--
horizontal <--
>
<FlatList
data={list?.slice(0, 10) || []}
keyExtractor={item => item.cc}
ItemSeparatorComponent={Separator}
renderItem={({ item }) => (
<Collaborator name={item.name} cc={item.cc} />
)}
/>
</ScrollView>
This worked for me (as a bit of a hack). Use a FlatList with empty data and null renderItem props instead of using a ScrollView
const emptyData = [];
const renderNullItem = () => null;
const App = () => {
const ListFooterComponent = (
<FlatList ... />
);
return (
<SafeAreaView>
<FlatList
data={emptyData}
renderItem={renderNullItem}
ListFooterComponent={ListFooterComponent}
/>
</SafeAreaView>
);
};
I had the same issue, and just got rid of it by removing the ScrollView around the FlatList. Because by default FlatList provides Scroll Functionality based on the length of content that it renders. 😊

React Native ref in Flatlist items. Returning for last item alone

I'm creating a list of collapsible using the react native flatlist component.
I'm using ref attribute to get the item clicked.
But when i try to access the ref from the click event, it doesn't take effect on the clicked item but on the last item in the flatlist.
export default class Update extends Component {
renderItems (data, index) {
return (
<TouchableNativeFeedback
onPress={() => this.expandView()}
>
<View style={[styles.itemWrapperExpandable]}>
<View style={styles.itemHeader}>
<View style={styles.itemAvatar}>
<Image source={require('../images/logo.png')} style={styles.avatar}></Image>
</View>
<View style={styles.itemContent}>
<Text style={[styles.itemTitle, styles.black]}>{data.name}</Text>
<Text style={[styles.rating, styles.grey]}>
{data.rating}<Icon name="star"></Icon>
</Text>
<Text style={[styles.content, styles.black]}>{data.description}</Text>
</View>
<View style={styles.itemBtn}>
<Icon name="chevron-down" style={{ color: '#000', fontSize: 22 }}></Icon>
</View>
</View>
<View ref={(e) => this._expandableView = e } style={[styles.itemBody]}>
<Text style={styles.itemBodyText}>
some more information about this update will appear here
some more information about this update will appear here
</Text>
</View>
</View>
</TouchableNativeFeedback>
);
}
expandView () {
LayoutAnimation.easeInEaseOut();
if (this._expandableView !== null) {
if (!this.state.isExpanded) {
// alert(this.state.isExpanded)
this._expandableView.setNativeProps({
style: {height: null, paddingTop: 15, paddingBottom: 15,}
})
}
else {
this._expandableView.setNativeProps({
style: {height: 0, paddingTop: 0, paddingBottom: 0,}
});
}
this._expandableView.setState(prevState => ({
isExpanded: !prevState
}));
}
}
render() {
return (
<FlatList
data={this.state.data}
renderItem={({ item, index }) => this.renderItems(item, index)}
/>
)
}
}
I also tried placing using the index of the items, but couldn't make it work.
Any way around this? I think the ref are overwritten by the next one when the items are rendering.
You are right about your assumption. Ref is overwriting with the next item so ref is the last item's ref. You can use something like below to set each items ref separately.
ref={(ref) => this.refs[data.id] = ref}
Of course this solution assumes you have a unique id or sort in your item data.
To paraphrase the React Native docs, direct manipulation (i.e. refs) should be used sparingly; unless you need it for some other reason I'm unaware of, refs aren't necessary in this case. Typically, the best way to keep track of selected items in a FlatList is by utilizing the keyExtractor and extraData props in conjunction with a Javascript Map object in state.
The way React is able to keep track of items being added/removed/modified is by using a unique key prop for each item (preferably an id, or if necessary indexes work if the list order will not change). In a FlatList, this is handled "automagically" if you will using the keyExtractor prop. To keep track of the selected item, we can add/remove items from our Map object whenever we click on one. Map is a type of object like an array that holds key-value pairs. We'll use this in state to store a key item.id and a boolean value true for each item that is selected.
So, we end up with something like this:
export default class Update extends Component {
state = {
data: [],
selected: (new Map(): Map<string, boolean>)
}
renderItems = ({ item }) => {
// note: the double ! operator is to make sure non boolean values are properly converted to boolean
return (
<ExpandableItem
item={item}
selected={!!this.state.selected.get(item.id)}
expandView={() => this.expandView(item)}
/>
);
}
expandView (item) {
LayoutAnimation.easeInEaseOut();
this.setState((state) => {
const selected = new Map(state.selected);
selected.set(item.id, !selected.get(item.id));
return {selected};
});
// the above allows for multiple expanded items at a time; the following will simultaneously close the last item when expanding a new one
// this.setState((state) => {
// const selected = new Map();
// selected.set(item.id, true);
// return {selected};
// });
}
render() {
return (
<FlatList
data={this.state.data}
keyExtractor={(item, index) => `${item.id}`}
renderItem={this.renderItems}
/>
);
}
}
const ExpandableItem = ({ item, selected, expandView }) => {
return (
<TouchableNativeFeedback onPress={expandView}>
<View style={styles.itemWrapperExpandable}>
{/* ...insert other header code */}
<View style={[styles.itemBody, selected && styles.itemBodySelected]}>
<Text style={styles.itemBodyText}>
some more information about this update will appear here
</Text>
</View>
</View>
</TouchableNativeFeedback>
);
}
You'll have to play around with the styles.itemBodySelected to make it look how you want. Note that the separate functional component <ExpandableItem /> for the renderItem isn't required, just how I prefer to structure my code.
Helpful links:
https://facebook.github.io/react-native/docs/flatlist.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
https://reactjs.org/docs/lists-and-keys.html#keys

Categories

Resources