I know that it has been asked several times, but in all the other threads the problem is that the author is manipulating the state directly - which I don't (hopefully).
I've got an array of posts that I get from the DB.
I want to be able to filter this array according to the tags each post has. In order to do so, I'm filtering the array, saving the result in a temp array, and then setting another array that holds the current posts to display again using useState. The filter works properly.
The list of posts is rendered in a FlatList
<FlatList
data={filteredPosts}
extraData={refreshFlat}
style={globalStyles.feed}
renderItem={({ item }) => (
<Post
post={item}
navigation={navigation}
style={globalStyles.list_of_posts}
/>
)}
refreshing={refreshing}
onRefresh={handleRefresh}
ListEmptyComponent={() => {
return (
<View>
<Text style={globalStyles.be_first}>
נראה שאין מה להציג כרגע..
</Text>
</View>
);
}}
ItemSeparatorComponent={() => {
return <View style={{ height: 12 }}></View>;
}}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={getHeader}
/>
But when I re-render the list, the wrong items are shown.
For example, if the list contains only one post after the filter, the FlatList will display only the first item of the original list.
Just to make clear, the item is the right item, I used console.log both outside and inside the Post component to validate it.
postsList - Holds the original list
filteredPosts - Holds the current posts that I want to display
refreshFlat - I tried to force it to refresh using extraData
The complete component:
import { Text, Pressable, FlatList, View, Modal } from "react-native";
import { globalStyles } from "../../styles/global";
import React, { useState, useEffect } from "react";
import Post from "../../API/Post";
import { useData } from "../../AuthProvider/UserDataProvider";
const FeedScreen = ({ navigation, route }) => {
const [refreshing, setRefreshing] = useState(true);
const { getPosts, tagsList, getTags } = useData();
const [postsList, setPostsList] = useState([]);
const [modalVisible, setModalVisible] = useState(false);
const [selectedTags, setSelectedTags] = useState([]);
const [filteredPosts, setFilteredPosts] = useState([]);
const [refreshFlat, setRefreshFlat] = useState(false);
const handleRefresh = () => {
getPosts()
.then((posts) => {
setPostsList(posts);
setFilteredPosts(posts);
setSelectedTags([]);
setRefreshing(false);
})
.catch(console.error);
};
const handleSelectTag = (tag) => {
if (selectedTags.includes(tag)) {
const temp = selectedTags.filter((currTag) => currTag !== tag);
setSelectedTags(temp);
} else {
setSelectedTags((prev) => [...prev, tag]);
}
};
const filterPosts = (tags) => {
if (tags.length === 0) return setFilteredPosts([...postsList]);
const temp = postsList.filter((post) =>
post.data.tags.some((t) => tags.includes(t))
);
console.log(temp);
setFilteredPosts(temp);
setRefreshFlat((prev) => !prev);
};
const getHeader = () => {
return (
<View>
<Modal
visible={modalVisible}
animationType="slide"
onRequestClose={() => {
setModalVisible((prev) => !prev);
}}
>
<View>
<FlatList
data={tagsList}
renderItem={({ item }) => (
<Pressable
style={{
backgroundColor: selectedTags.includes(item.name)
? "green"
: "#EAE7E6",
padding: 5,
margin: 5,
}}
onPress={() => handleSelectTag(item.name)}
>
<Text>{item.name}</Text>
</Pressable>
)}
numColumns={3}
ListEmptyComponent={() => {
return (
<View>
<Text style={globalStyles.be_first}>
נראה שאין מה להציג כרגע..
</Text>
</View>
);
}}
ItemSeparatorComponent={() => {
return <View style={{ height: 12 }}></View>;
}}
keyExtractor={(item, index) => index.toString()}
/>
</View>
<Pressable
onPress={() => {
filterPosts(selectedTags);
setModalVisible(false);
}}
style={{ marginLeft: 10, width: 50, height: 50 }}
>
<Text>{"סנן"}</Text>
</Pressable>
</Modal>
<Pressable
onPress={() => setModalVisible(true)}
style={{ width: "100%", height: 50 }}
>
<Text>{"open modal"}</Text>
</Pressable>
</View>
);
};
useEffect(() => {
getPosts()
.then((posts) => {
setPostsList(posts);
setFilteredPosts(posts);
setRefreshing(false);
})
.catch(console.error);
getTags();
return;
}, []);
return (
<View style={{ flex: 1 }}>
<FlatList
data={filteredPosts}
extraData={refreshFlat}
style={globalStyles.feed}
renderItem={({ item }) => (
<Post
post={item}
navigation={navigation}
style={globalStyles.list_of_posts}
/>
)}
refreshing={refreshing}
onRefresh={handleRefresh}
ListEmptyComponent={() => {
return (
<View>
<Text style={globalStyles.be_first}>
נראה שאין מה להציג כרגע..
</Text>
</View>
);
}}
ItemSeparatorComponent={() => {
return <View style={{ height: 12 }}></View>;
}}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={getHeader}
/>
<Pressable
title="edit"
onPress={() => {
navigation.navigate("CreateProject");
}}
style={globalStyles.plus_btn}
>
<Text style={globalStyles.plus_btn_text}>+</Text>
</Pressable>
</View>
);
};
export default FeedScreen;
Maybe it's the key extractor issue? You are using the index of the array as the key and it could confuse the flatlist when you update the data.
Related
I am having a problem that I can't solve by my own. I am making an app for making lists in React Native, and in my main screen, that shows the session initiated by the user, I have to render all the lists that he had saved previously. Here is the code of my session component.
export default function Session({navigation,route}){
const {user} = useContext(myContext)
const [modalVisible, setModalVisible] = useState(false)
const [lists, setLists] = useState(route.params.lists)
let keyListCounter = 0
const handleButton = async () => {
await AsyncStorage.removeItem("token")
navigation.navigate("Login")
}
const updateList = (title,newElement) => {
axios.put(`http://${REACT_APP_BACK_URI}/api/lists/add-list-element`, {nickname: user,title,element: newElement})
.then(res => {
if (res.status == 200) {
setLists(res.data.userLists)
}
})
.catch(err => console.log(err))
}
useEffect(() => {
navigation.setOptions({
title: user,
headerTitleAlign: "left",
headerRight: () => (
<TouchableWithoutFeedback onPress={() => handleButton()}>
<Text style={styles.logoutText}>Logout</Text>
</TouchableWithoutFeedback>
)
})
},[navigation,user])
return (
<View style={styles.mainContainerView}>
<ScrollView style={styles.mainContainerScrollView}>
<View style={styles.textListContainer}>
<Text style={styles.listsText}>LISTAS ACTIVAS: </Text>
<Text style={styles.numberListsText}>{lists.length}</Text>
</View>
{lists.map(elem => <List key={keyListCounter++} list={elem} updateList={updateList}/>)}
</ScrollView>
<Pressable style={styles.newListPressable} onPressIn={() => setModalVisible(true)}>
<Text style={styles.newListText}>+</Text>
</Pressable>
<View style={styles.centeredView}>
<Modal
visible={modalVisible}
animationType="slide"
transparent={true}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text>MODAL</Text>
</View>
</View>
</Modal>
</View>
</View>
)
}
My question is why after I actualize the state of "lists", whose elements are passed as props to the List component, the virtualized list that I have in the List component does not re-renderizes automaticaly.
Here I show also the code of the List component.
export default function List({list,updateList}){
const {elements, title} = list
let elementId = 0
const virtualizedList = useRef()
const [showVirtualizedList, setShowVirtualizedList] = useState("none")
const [showDownArrow, setShowDownArrow] = useState(true)
const [showUpArrow, setShowUpArrow] = useState(false)
let [newElementArray, setNewElementArray] = useState([])
let [listElements, setListElements] = useState(elements)
const getItem = (item) => ({
id: elementId++,
title: item
});
//List Pressable Events
const handlePressIn = () => {
if (showVirtualizedList == "none") setShowVirtualizedList("flex")
else setShowVirtualizedList("none")
setShowDownArrow(!showDownArrow)
setShowUpArrow(!showUpArrow)
}
//New element Pressable Events
const handleNewElement = () => {
setNewElementArray([...newElementArray,uuid.v4()])
}
//NewListItem TouchableWithoutFeedback Events
const deleteElementInput = newItemID => {
const elementsArray = newElementArray.filter(elem => elem != newItemID)
setNewElementArray(elementsArray)
}
const addListElement = (newElement,newItemID) => {
updateList(title,newElement)
deleteElementInput(newItemID)
}
useEffect(() => {
virtualizedList.current.setNativeProps({display: showVirtualizedList})
LogBox.ignoreLogs(['VirtualizedLists should never be nested']);
},[showVirtualizedList, virtualizedList])
return (
<ScrollView style={styles.mainContainer}>
<Pressable
style={styles.listElement}
onPressIn={() => handlePressIn()}
>
<View style={styles.titleContainer}>
<Text style={styles.listElementText}>{title} </Text>
<Text style={styles.listElementQuantity}>({listElements.length})</Text>
</View>
<View>
<DownArrow show={showDownArrow}/>
<UpArrow show={showUpArrow}/>
</View>
</Pressable>
<View>
<VirtualizedList
data={listElements}
initialNumToRender={10}
getItemCount={() => listElements.length}
renderItem={({item}) => <ListItem item={item}/>}
getItem={() => getItem(listElements[elementId])}
ref={virtualizedList}
/>
</View>
{newElementArray.length > 0 ? newElementArray.map(elem => {
return (
<NewListItem
key={elem}
id={elem}
newElementArray={newElementArray}
deleteElementInput={deleteElementInput}
addListElement={addListElement}
/>
)
}) : ""
}
<Pressable style={styles.newElementPressable} onPressIn={() => handleNewElement()}>
<Text style={styles.newElementText}>+</Text>
</Pressable>
</ScrollView>
)
}
UPDATE: I solve the problem using a FlatList instead of a VirtualizedList. For some reason the FlatList re-renders when the Item is updated and the VirtualizedList no. I don't know why.....
I'm calling API and seeing the respective API values in the log, it shows me correct values, but when I try to set API in Flat list with the help of hooks my app crashes. I don't know the reason as I'm new in react native, so any help regarding this would be really appreciated.
NOTE( If I'm displaying the values directly without flat list it won't cause any error)
function Item({ item }) {
const navigation = useNavigation();
return (
<TouchableOpacity style={styles.listItemBox}
onPress={() => navigation.navigate('PatientDemographics')}
>
<View style={{flex:1}}>
<Text numberOfLines={1} style={{ textAlign: 'left', fontSize: 25, color:"#075430", textAlign: 'center',fontFamily:"Montserrat-Regular"}}>{item.firstName}</Text>
<TouchableOpacity style={[styles.smallRoundedBlueRoundedNoMargin,{marginTop:10,marginBottom:40}]}
onPress={() => navigation.navigate('PatientDemographics')} >
<Text style={[styles.cardText,{fontSize: 18},{color: 'white'}]}>SELECT </Text>
</TouchableOpacity>
</View>
</TouchableOpacity>
);
}
const SelectPatient = () => {
let numColumns = 4;
const formatData = (data, numColumns) => {
const numberOfFullRows = Math.floor(data.length / numColumns);
let numberOfElementsLastRow = 8 - (numberOfFullRows * numColumns);
while (numberOfElementsLastRow !== numColumns && numberOfElementsLastRow !== 0) {
data.push({ key: `blank-${numberOfElementsLastRow}`, empty: true });
numberOfElementsLastRow++;
}
return data;
};
// const navigation = useNavigation();
const [isLoading, setLoading] = useState(true);
const [patient, setPatient] = useState([]);
const mrnum=89
useEffect(() => {
axios({
method: 'get',
url: `https://emr-system.000webhostapp.com/emrappointment/emrappointment/patient/search?mrnum=89&cnic=&qrcode=`,
}).then((response) => {
//Balance / transaction-list
setPatient(response.data.result);
console.log(response.data.result);
console.log(patient[0].patientId);
}).then(() => setLoading(false));
}, []);
return (
<View style={styles.container}>
<Header name="Select Patient" class= ""/>
<UnitClerkHeader/>
<PatientHeader/>
<View style= {{flex:1 ,width: '100%', alignSelf: 'center'}}>
<SafeAreaView style={{flex:1}} >
<FlatList
style={{flex:1, marginTop: 30, marginRight:30,marginLeft:30}}
data={ formatData(patient,numColumns)}
renderItem={({ item }) => <Item item={item}/>}
keyExtractor={item => item.patientId}
numColumns = {numColumns}
/>
</SafeAreaView>
</View>
</View>
);
}
export default SelectPatient;
You can try with
<FlatList
style={{ flex: 1, marginTop: 30, marginRight: 30, marginLeft: 30 }}
data={() => formatData(patient, numColumns)}
renderItem={({ item }) => <Item item={item} />}
keyExtractor={item => item.patientId}
numColumns={numColumns}
/>
I can help you better when you show your error too.
I am trying to add items to redux state array. I can add items but my flatlist doesn't display them. It's most likely because they are like this ['abc-123, bcd-234'] etc. instead of [{license: abc-123}] so I could call the item.license in my flatlist. And how would I add an id to these items. How can I fix my structure a bit to get the [{id: 0, license: 'abc-123'}] ?
This is my action file:
const ADD_NEW_CAR = 'ADD_NEW_CAR'
const DELETE_EXISTING_CAR = 'DELETE_EXISTING_CAR'
export const addNewCar = (text) => ({
type: ADD_NEW_CAR,
payload: text
})
export const deleteExistingCar = (license) => ({
type: DELETE_EXISTING_CAR,
payload: license
})
this is my reducer:
const ADD_NEW_CAR = 'ADD_NEW_CAR'
const DELETE_EXISTING_CAR = 'DELETE_EXISTING_CAR'
const initialState = {
cars: [],
}
const carsListReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_NEW_CAR:
return {
...state,
cars: [...state.cars, action.payload],
}
case DELETE_EXISTING_CAR:
return {
cars: [
...state.cars.filter(license => license !== action.payload)
]
}
default:
return state
}
}
export default carsListReducer
and this is my flatlist:
const totalCars = props.cars.length
<FlatList
style={{ marginTop: 0 }}
data={cars}
keyExtractor={(item) => item.id}
renderItem={({ item }) => {
return (
<View style={licenseContainer}>
<View style={{ width: '20%' }}>
<Ionicons
style={carIcon}
name='car-outline'
size={30}
color={colors.black}
/>
</View>
<View style={{ width: editing ? '60%' : '80%' }}>
<Text key={item.license} style={licenseText}>
{item.license}
</Text>
</View>
{editing ? (
<View style={{ width: '20%' }}>
<Ionicons
name='ios-close-circle-outline'
size={30}
color={colors.black}
style={removeIcon}
onPress={() => removeCar(item.license)}
/>
</View>
) : null}
</View>
)
}}
ItemSeparatorComponent={() => {
return <View style={divider} />
}}
/>
const mapStateToProps = (state) => ({
signedIn: state.authReducer.signedIn,
cars: state.cars
})
const mapDispatchToProps = (dispatch) => ({
authActions: bindActionCreators(authAction, dispatch),
addNewCar: text => dispatch(addNewCar(text)),
deleteExistingCar: car => dispatch(deleteExistingCar(car))
})
export default connect(mapStateToProps, mapDispatchToProps)(ProfileScreen)
If your array contains string values then instead of using item.license just use item
<View style={{ width: editing ? '60%' : '80%' }}>
<Text key={item} style={licenseText}>
{item}
</Text>
</View>
I use this to collect data from my server
getPopularProduct = async () => {
const url = `https://swissmade.direct/wp-json/swissmade/home/popular`;
fetch(url)
.then(response => response.json())
.then((responseJson) => {
console.log(responseJson.products.thumbnail, "details")
if(responseJson.error == false){
this.setState({
dataSourcePopularProduct: responseJson.products,
//isLoading: false,
})
}
})
.catch((error) => {
console.log(error)
})
}
this the blank space imag and this is the flat list and render item
renderPopularProduct = ({ item }) => {
const entities = new Entities();
var id = item.pid;
var img = { 'uri': item.thumbnail };
return (
<TouchableOpacity onPress={() => this.props.navigation.navigate('ProductDetails', { id })}>
<View style={styles.GalleryBox}>
<View style={styles.GalleryImg} onPress={() => this.props.navigation.navigate("ProductDetails")}>
<Image source={img} style={styles.SingelImg} largeHeap="true"/>
</View>
<View style={styles.GalleryText}>
<Text style={styles.userNmae}>{item.title}</Text>
</View>
<View style={styles.amount}>
<Text style={styles.userNmae}>{entities.decode(item.currency)} {item.price}</Text>
</View>
</View>
</TouchableOpacity>
)
}
<FlatList
data={this.state.dataSourcePopularProduct}
renderItem={this.renderPopularProduct}
keyExtractor={(item, index) => index}
horizontal={true}
showsHorizontalScrollIndicator={false}
/>
and stylesheet
SingelImg: {
width: '150%',
height: '120%',
//resizeMode: 'cover',
marginLeft: -25
},
And this way I use flat list but when it shows some data are missing especially image. but my API returns every right data.
I've made a chat app, and for rendering messages flatlist is used. But the problem is tried to scroll to the end of the screen every time the page is loaded, but it fails to do so. I've tried inverted props, but nothing happened, only the list got inverted.
Even played with ref to make it auto-scroll to the bottom, but nothing happened.
<FlatList
ref="flatList"
onContentSizeChange={() =>
this.refs.flatList.scrollToEnd()}
contentContainerStyle={{
marginBottom:
verticalScale(200)
}}
style={styles.list}
data={this.state.messages}
/>
How to make it scroll to the bottom the screen or scroll to the last index of the message when rendered?
(UPDATE)
IT WAS AN ISSUE WITH THE <Content/> component i used which belongs to native-base . Upon removing and replacing it with a <View/> it works perfectly fine.
Also, for chat based app the inverted prop in Flatlist is the way to implement in right way.
I've added the way i managed to scroll in the answer below. If you simply want your app to display the last item in the list and stays there, you can use inverted
You should use ref like this:
export default class MyAwesomeComponent extends React.Component {
FlatListRef = null; // add a member to hold the flatlist ref
render() {
return (
<FlatList
ref={ref => (this.FlatListRef = ref)} // assign the flatlist's ref to your component's FlatListRef...
onContentSizeChange={() => this.FlatListRef.scrollToEnd()} // scroll it
contentContainerStyle={{marginBottom: verticalScale(200)}}
style={styles.list}
data={this.state.messages}
/>
);
}
}
prueba esto
return (
<View style={{flex: 1}}>
<KeyboardAvoidingView
behavior="padding"
style={styles.keyboard}
keyboardVerticalOffset={height - 1000}>
<FlatList
ref={ref => (this.FlatListRef = ref)}
onContentSizeChange={() => this.FlatListRef.scrollToEnd()} // scroll it
// contentContainerStyle={{marginBottom: verticalScale(200)}}
// keyboardShouldPersistTaps='always'
style={styles.list}
extraData={this.state}
data={this.state.messages}
keyExtractor={item => {
return item.id;
}}
renderItem={e => this._renderItem(e)}
/>
<View style={styles.input}>
<TextInput
// style={{flex: 1}}
value={msg}
placeholderTextColor="#000"
onChangeText={msg => this.setState({msg: msg})}
blurOnSubmit={false}
onSubmitEditing={() => this.send()}
placeholder="Escribe el mensaje"
returnKeyType="send"
/>
</View>
</KeyboardAvoidingView>
</View>
);
You can use Javascript method to reverse to show your messages from end
messages.reverse()
scrollToListPosition = (index) => {
const itemOffset = this.getItemOffset(index)
this.flatListRef.scrollToOffset({ animated: false, offset: itemOffset })
}
getItemOffset = (index) => {
let heightsum = 0
for (i = 0; i < index; i++) {
heightsum = heightsum + this.itemHeight[i]
}
return heightsum
}
render(){
return (
<FlatList
ref={(ref) => { this.flatListRef = ref; }}
data={postList}
keyExtractor={(item, index) => item._id}
horizontal={false}
extraData={this.state}
keyboardShouldPersistTaps='always'
refreshing={this.props.isRefreshing}
onRefresh={this.handleRefresh}
onEndReached={this.handleLoadMore}
getItemLayout={(data, index) => (
{ length: this.getLength(index), offset: this.getLength(index) * index, index }
)}
renderItem={({ item, index }) => {
return (
<View onLayout={(event) => {
var { height } = event.nativeEvent.layout;
this.itemHeight[index] = height
}}
>
<ListCommon
key={index}
item={item}
index={index}
parentFlatList={this}
data={item}
instance={this.props.commanAction}
tag={this.state.tag}
changeRoute={this.props.changeRoute}
/>
</View>
);
}}
/>
)
}
getLength = (index) => {
if (this.itemHeight[index] === undefined) {
return 0;
}
return this.itemHeight[index]
}
Here is how i solved it:
export default class Test extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
setTimeout(() => {
this.FlatListRef.scrollToEnd();
}, 1500);
}
render() {
return (
<View style={{ flex: 1 }}>
<FlatList
data={[1, 2, 3, 4, 5, 6, 7, 8]}
ref={(ref) => (this.FlatListRef = ref)}
renderItem={({ item }) => {
return (
<View
style={{
height: 140,
width: 400,
backgroundColor: "yellow",
alignItems: "center",
justifyContent: "center",
}}
>
<Text>{item}</Text>
</View>
);
}}
/>
</View>
);
}
}