React Native: Conditional props for List - react-native

I was wondering if there is a way to set a group of properties for a list component based on a condition.
render() {
return (
<SectionList
ref={(ref) => {this.listRef = ref}}
style={styles.listContainer}
sections={this.props.listData}
fetchData={this.props.fetchData}
keyExtractor={this.keyExtractor}
renderSectionHeader={this.renderSectionHeader}
renderItem={this.renderItem}
ItemSeparatorComponent={this.separator}
keyboardDismissMode='on-drag'
initialNumToRender={6}
stickySectionHeadersEnabled={false}
// ListFooterComponent={this.renderFooter}
// onEndReachedThreshold={0.5}
// onEndReached={() => {
// this.props.fetchMoreData()}}
/>
)
}
I would like ListFooterComponent, onEndReachedThreshold and onEndReached only to be set if this.props.fetchMoreData is set. Do I need to put an if-statement at the top of the render function or is it possible to create a separate prop to group those 3 and use the spread operator to put this back into the SectionList based on the condition. I am not quite sure how to do this.

I ended up with the following
render() {
optional = {}
if (this.props.fetchMoreData) {
optional.onEndReachedThreshold = 0.5
optional.onEndReached = () => {
this.props.fetchMoreData()}
optional.ListFooterComponent = this.renderFooter
}
return (
<SectionList
ref={(ref) => {this.listRef = ref}}
style={styles.listContainer}
sections={this.props.listData}
fetchData={this.props.fetchData}
keyExtractor={this.keyExtractor}
renderSectionHeader={this.renderSectionHeader}
renderItem={this.renderItem}
ItemSeparatorComponent={this.separator}
keyboardDismissMode='on-drag'
initialNumToRender={6}
stickySectionHeadersEnabled={false}
{...optional}
/>
)
I think I initially tripped over optional.ListFooterComponent. It just looked strange to me so I assumed it must be wrong ;)

Related

How do I setState from toggle checkbox?

The setState is not working when I am trying to use the onToggle from the checkbox to set the state to true.
I have tried to console log the function and it works except that the state won't change.
import CircleCheckBox, { LABEL_POSITION } from 'react-native-circle-checkbox';
const MyTaskItem = (props) => {
return (
<CircleCheckBox
checked={props.taskDone}
onToggle={props.onToggleCheck}
/>
);
};
import MyTaskItem from "../../components/MyTaskItem";
export default function MyTasksScreen() {
const [taskDone, setTaskDone] = useState(false);
const checkDoneHandler = () => {
setTaskDone(true);
console.log(setTaskDone);
};
return (
<View style={styles.screen}>
<MyTaskInput visible={isAddMode} onAddTask={addTaskHandler} onCancel={cancelTaskAdditionHandler} />
<FlatList
keyExtractor={(item, index) => item.id}
data={myTasks}
renderItem={itemData =>
<MyTaskItem
id={itemData.item.id}
onDelete={removeTaskHandler}
title={itemData.item.title}
description={itemData.item.description}
onToggleCheck={checkDoneHandler}
/>}
/>
<AddTodoButton onPress={() => setIsAddMode(true)} />
</View>
);
}
I want to change the state on taskDone to true/false when toggling the checkbox.
I guess you want to change specific task state, so first of all, you need to pass the ID of the task which toggled, so MyTaskItem can be rewritten like this:
const MyTaskItem = (props) => {
const {taskDone, id, onToggleCheck} = props;
return (
<CircleCheckBox
checked={taskDone}
onToggle={()=>onToggleCheck(id)}
/>
);
};
Then you should handle items states in your MyTasksScreen.
Since FlatList is a PureComponent, therefore you need to pass the changing states to the extraData property of the flatlist, to inform item states has changed.
In case you want to change all items done state, you can do like this:
export default function MyTasksScreen() {
const [taskDone, setTaskDone] = useState(false);
const checkDoneHandler = () => {
setTaskDone(true);
console.log(setTaskDone);
};
return (
<View style={styles.screen}>
<MyTaskInput visible={isAddMode} onAddTask={addTaskHandler} onCancel={cancelTaskAdditionHandler} />
<FlatList
keyExtractor={(item, index) => item.id}
data={myTasks}
extraData={taskDone}
renderItem={itemData =>
<MyTaskItem
id={itemData.item.id}
onDelete={removeTaskHandler}
taskDone={taskDone}
title={itemData.item.title}
description={itemData.item.description}
onToggleCheck={checkDoneHandler}
/>}
/>
<AddTodoButton onPress={() => setIsAddMode(true)} />
</View>
);
}
I'd advise you to use TypeScript.
From the code you've added, looks like you're not sending taskDone to your MyTaskItem Component.
Also, try adding console.log(taskDone) after this line: const [taskDone, setTaskDone] = useState(false);

React Native: Lodash Map

I'm new in react native and I want make data function into view.
my function looks like this
renderTest = () => {
<FlatList
onEndReached={0}
onEndReached={() => this.handleEnd()}
>
{_.map(this.state.leads, (leads, index) => {
return (
<Text key={index}>{leads.full_name}</Text>
)
})}
</FlatList>
}
and my View to pass the value of the function
<View style={{flexDirection: 'row'}}>
{this.renderTest()}
</View>
I don't have any idea what's the problem all I just want is to render the value. I hope could someone help me.
edited
Since you already are in react world you can simply use the Array.map:
renderTest = () => {
<FlatList onEndReached={() => this.handleEnd()}>
{this.state.leads.map((lead, index) => {
return (<Text key={index}>{lead.full_name}</Text>)
})}
</FlatList>
}
As long as this.state.leads is an array.
But bottom line is no lodash is needed here.
As seen by your comment on Akrion's answer, I'm guessing you haven't defined the renderTest function inside the component. Another possibility is that you are using a stateless component in which case you cannot access this
I'm just not sure you can define a FlatList like that one. I mean, I think you're missing the return of the function and you have to pass the property data to FlatList this way:
renderTest = () => {
return (
<FlatList
onEndReached={0}
onEndReached={() => this.handleEnd()}
data={this.state.leads}
renderItem={(lead, index) => <Text key={index}>{lead.full_name}</Text>}
/>
)
}
Let me know if this can solve your issue :)

React-Native FlatList render a zero state when data is empty

Is it possible to render an alternate component when the data is empty? The only reason I would not just render the list or not render the list is that the ListHeaderComponent is necessary in both scenarios (data.length and !data.length)...
const data = []
<FlatList
contentContainerStyle={styles.list}
data={data} // if empty or !data.length render <ZeroComponent/>
UPDATE
react-native recently added ListEmptyComponent
const data = []
_listEmptyComponent = () => {
return (
<View>
// any activity indicator or error component
</View>
)
}
<FlatList
data={data}
ListEmptyComponent={this._listEmptyComponent}
contentContainerStyle={styles.list}
/>
const data = []
renderFooter = () => {
if (data.length != 0) return null;
return (
<View>
// any activity indicator or error component
</View>
);
};
<FlatList
contentContainerStyle={styles.list}
data={data}
ListFooterComponent={this.renderFooter}
/>
Hope this helps
There is a prop ListEmptyComponent which can do the job.
<FlatList
data={this.state.data}
renderItem={this.renderItem}
keyExtractor={(item, index) => item.id}
ListHeaderComponent={this.showFilters()}
ListEmptyComponent={this.showEmptyListView()}
/>
You can do something like this
{data.length > 0 ? <FlatList ... /> : <EmptyList />}
Hope it helps

Call this.props in function outside render gives undefine

Im having this problem with my flatlist. Basically im destructing my code to make it more readable. However my problem whenever I call the this.props outside the render it gives my undefine. Here's my code below:
renderItem(item) {
console.log(this.props) // Gives me undefine
return (
<ListItem button>
<Text>{item.item.name }</Text>
</ListItem>
)
}
render() {
return (
<FlatList
keyboardShouldPersistTaps={'always'}
data={countries.payload.data}
renderItem={this.renderItem}
keyExtractor={item => item.name}
/>
)
}
But if I did not separate to other function the renderItem. I can access the this.props:
render() {
return (
<FlatList
keyboardShouldPersistTaps={'always'}
data={countries.payload.data}
renderItem={({item, index}) => (
<ListItem button onPress={() => this.props.onPressAction()}> // I get the correct value
<Text>{item.name }</Text>
</ListItem>
)}
keyExtractor={item => item.name}
/>
)
}
When using react-native and ES6 classes it won't automatically bind your functions that is declared on your classes, so this will be a live object represent for the unknown context or even undefined which caused this.props to be undefined.
We need to handle the correct value of this to our methods on our own. Therefore either use:
Change to this.renderItem.bind(this)
Or use arrow functions like renderItem= () => {}

ScrollToEnd after update data for Flatlist

I'm making a chat box with Flatlist. I want to add a new item to data then scroll to bottom of list. I use scrollToEnd method but it did not work. How can I do this?
<FlatList
ref="flatList"
data={this.state.data}
extraData = {this.state}
renderItem={({item}) => <Text style={styles.chatFlatListItem}>{item.chat}</Text>}
/>
AddChat(_chat){
var arr = this.state.data;
arr.push({key: arr.length, chat: _chat});
var _data = {};
_data["data"] = arr;
this.setState(_data);
this.refs.flatList.scrollToEnd();
}
I found a better solution,scrollToEnd() is not working because it is triggered before the change is made to the FlatList.
Since it inherits from ScrollView the best way here is to call scrollToEnd() in onContentSizeChange like so :
<FlatList
ref = "flatList"
onContentSizeChange={()=> this.refs.flatList.scrollToEnd()} />
Thanks #Kernael, just add a timeout like so:
setTimeout(() => this.refs.flatList.scrollToEnd(), 200)
const flatList = React.useRef(null)
<FlatList
ref={flatList}
onContentSizeChange={() => {
flatList.current.scrollToEnd();
}}
data={this.state.data}
extraData = {this.state}
renderItem={({item}) => <Text style={styles.chatFlatListItem}>{item.chat}</Text>}
/>
try this,it works.
My issue here was that scrollToEnd() worked fine on mobile but on web it always scrolled to the top. Probably because I have elements with different size in the FlatList and couldn't define getItemLayout. But thanks to the accepted answer here I solved it. Just with different approach.
const ref = React.useRef<FlatList>();
function handleScrollToEnd(width, height) {
if (ref.current) {
ref.current.scrollToOffset({offset: height});
}
}
<FlatList
ref={ref}
onContentSizeChange={handleScrollToEnd}
/>
This works great on both the mobile and web. Hope it helps to somebody.
Change your code as below. The ref is modified and It's better to use getItemLayout in your FlatList according to this.
AddChat(_chat){
var arr = this.state.data;
arr.push({key: arr.length, chat: _chat});
var _data = {};
_data["data"] = arr;
this.setState(_data);
this.flatList.scrollToEnd();
}
<FlatList
ref={elm => this.flatList = elm}
data={this.state.data}
extraData = {this.state}
renderItem={({item}) => <Text style={styles.chatFlatListItem}>{item.chat}</Text>}
getItemLayout={(data, index) => (
{length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
)}
/>
Note: Replace the ITEM_HEIGHT with the real value of height of your list items.
Try to use inverted prop on Fatlist itself
Pass your data like this [...data].reverse()
If you are at the middle of list and you need to scroll to end when a new item is added, just use:
ref => flatlistRef.current?.scrollToOffset({offset:0})
seems caused by this line
https://github.com/facebook/react-native/blob/3da3d82320bd035c6bd361a82ea12a70dba4e851/Libraries/Lists/VirtualizedList.js#L1573
when use trigger scrollToEnd, frame.offset is 0
https://github.com/facebook/react-native/blob/3da3d82320bd035c6bd361a82ea12a70dba4e851/Libraries/Lists/VirtualizedList.js#L390
if you wait 1 second, _onContentSize changes and frame.offset is valorized (for ex. 1200 px).
Related post https://github.com/facebook/react-native/issues/30373#issuecomment-1176199466
Simply add a loader before Flatlist renders. For example:
const flatListRef = useRef(null);
const [messages, setMessages] = useState([]);
if(!messages.length){
return <Loader />
}
return (
<View style={styles.messagesContainer}>
<FlatList
ref={flatListRef}
data={messages}
onContentSizeChange={() => {
if (flatListRef.current) {
flatListRef?.current?.scrollToEnd();
}
}}
renderItem={({item, index}) => {
return (
<DisplayMessages
message={item}
index={index}
/>
);
}}
/>
</View>