app crashes because of Flat-List API calling - react-native

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.

Related

How can I get current index through FlatList?

I'm passing data to details screen with Navigation params, everything works correct, the only thing that I want to pass clicked current level index to Object.entries[currentIndex], I'm not sure how can I get that.
<FlatList
data={data}
keyExtractor={(item) => item.index}
renderItem={({ item, index }) => (
<View>
{item.sTask?.map((levels, i) => (
<View>
<TouchableOpacity
onPress={() => navigation.navigate('Tasks', { item })}
style={{
backgroundColor: '#33333325',
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 10,
marginVertical: 20,
marginHorizontal: 20,
}}
>
<Text>{Level 1}</Text>
</TouchableOpacity>
<TouchableOpacity>
{Another level...}
</TouchableOpacity>
</View>
))}
</View>
)}
/>
Details screen:
const [currentTaskGroupIndex, setCurrentTaskGroupIndex] = useState(0); <---- We need to set current index somehow to current clicked index
//Test item params data
useEffect(() => {
const mapData = item?.sTask?.map((tasks) => {
return Object.entries(tasks)[currentTaskGroupIndex].map((level) => {
// console.log(level);
});
});
}, [item]);
No matter which level i clicked, i'm getting Level 1 details
You need to do like :
onPress={() => navigation.navigate('Tasks', { item:item, index:i })}
and then in details screen:
const indexParam = props?.route?.params?.index ?? 0
const [currentTaskGroupIndex, setCurrentTaskGroupIndex] = useState(indexParam); <---- We need to set current index somehow to current clicked index
//Test item params data
useEffect(() => {
const mapData = item?.sTask?.map((tasks) => {
return Object.entries(tasks)[currentTaskGroupIndex].map((level) => {
// console.log(level);
});
});
}, [item]);
Hope it helps. feel free for doubts
You can pass index of the task in navigation param
onPress={() => navigation.navigate('Tasks', { item, index: i })}
Now, you no need map() to find your task
const mapData = item?.sTask[index]
you can pass your whole object in the navigation params
Thank you guys for you answers, you were right, and plus i jusd added another Object.key().map and it worked
{item.sTask?.map((levels, i) => (
<View key={i}>
{Object.keys(levels).map((key, i) => (
<View key={i}>
{console.log(key)}
<TouchableOpacity
onPress={() =>
navigation.navigate('Tasks', { item: item, index: i })
}
style={{
backgroundColor: '#33333325',
paddingHorizontal: 20,
paddingVertical: 10,
borderRadius: 10,
marginVertical: 20,
marginHorizontal: 20,
}}
>
<Text>{key}</Text>
</TouchableOpacity>
</View>
))}
```

Remove an item from FlatList render wrong items

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.

How do I add items to array in following format and display it in flatlist?

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>

Why is AsyncStorage not retrieving data once I refresh my App?

I am building a todo app and I am trying to store and retrieve data but it's not retrieving any data that is being stored. Once I refresh the data doesn't seem to persist. If there is another way of storing or writing my code please assist. I tried using other methods of storage like MMKV but it was just similar to AsyncStorage so I decided to stick with AsyncStorage. Here is my code:
import AsyncStorage from "#react-native-async-storage/async-storage";
export default function todaytodo() {
const [modalOpen, setModalOpen] = useState(false);
const [todos, setTodos] = useState("");
const storedata = async () => {
try {
await AsyncStorage.setItem("Todos", JSON.stringify(todos));
} catch (err) {
console.log(err);
}
};
const loadData = async () => {
try {
const value = await AsyncStorage.getItem("Todos");
if (value !== null) {
console.log(value);
return value;
}
} catch (error) {
console.log(error);
}
};
useEffect(() => {
storedata();
loadData();
});
const toggleComplete = (index) =>
setTodos(
todos.map((Todo, k) =>
k === index ? { ...Todo, complete: !Todo.complete } : Todo
)
);
const pressHandler = (key) => {
setTodos((prevTodos) => {
return prevTodos.filter((todo) => todo.key != key);
});
};
const submitHandler = (Todo) => {
Todo.key = Math.random().toString();
setTodos((currentTodo) => {
return [Todo, ...currentTodo];
});
setModalOpen(false);
};
return (
<View style={styles.container}>
<View>
<View>
<Ionicons
style={{
position: "absolute",
marginTop: 650,
alignSelf: "flex-end",
zIndex: 10,
marginRight: 5,
}}
name="md-add-circle-outline"
size={73}
color="black"
onPress={() => setModalOpen(true)}
/>
</View>
<FlatList
data={todos}
renderItem={({ item, index, complete }) => (
<TouchableOpacity onPress={() => toggleComplete(index)}>
<ScrollView>
<View style={styles.everything}>
<View style={styles.itemlist}>
<Checkbox
label="delete"
checked={true}
onPress={() => pressHandler(item.key)}
/>
<Text
style={{
marginLeft: 8,
marginTop: 5,
fontSize: 15,
textDecorationLine: item.complete
? "line-through"
: "none",
color: item.complete ? "#a9a9a9" : "black",
}}
>
{item.Todo}
</Text>
</View>
<Text
style={{
fontSize: 12,
marginLeft: 50,
marginTop: -15,
color: "#008b8b",
textDecorationLine: item.complete
? "line-through"
: "none",
color: item.complete ? "#a9a9a9" : "#008b8b",
}}
>
{item.Comment}
</Text>
</View>
</ScrollView>
</TouchableOpacity>
)}
/>
</View>
<View style={styles.modalcont}>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<RNModal visible={modalOpen} animationType="slide">
<View style={styles.modalContent}>
<Ionicons
name="md-close-circle-outline"
style={{ alignSelf: "center" }}
size={60}
color="black"
onPress={() => setModalOpen(false)}
/>
<AddForm submitHandler={submitHandler} />
</View>
</RNModal>
</TouchableWithoutFeedback>
</View>
</View>
);
}
Use of useEffect is suspicious here, If you want to do it once on load of component
then need to update code for useEffect.
useEffect(() => {
storedata();
loadData();
}, []);

How to Update state in return()?

I want to change the state when I get id of 5/6 or 7, after TouchableOpacity. Because I want to show the "Other Things" button one time whether id is 5/6 or 7.
export function HomePage({ navigation }) {
const [parentServiceCheck, setParentServiceCheck] = useState({ check: '0' });
return (
<View>
<FlatList numColumns={2} contentContainerStyle={{ margin: 7, flex: 1 }} keyExtractor={(item, index) => item.id} data={allService} renderItem={itemData => (
<View style={{ flexDirection: 'column', flex: 1 }}>
{
(itemData.item.id == 5 || itemData.item.id == 6 || itemData.item.id == 7) && parentServiceCheck.check == '0' ?
<TouchableOpacity onPress={() => pressHandler(itemData.item.id)} style={{ margin: 7 }}>
<Card titlebtn="Other Things" src={itemData.item.service_image} sty={{ height: 180, }} />
</TouchableOpacity>
// Now here I want to setParentServiceChildCheck({check: '1'}) when itemData.item.id has 5/6/7
:
<TouchableOpacity onPress={() => pressHandler(itemData.item.id)} style={{ margin: 7 }}>
<Card titlebtn={itemData.item.service_name} src={itemData.item.service_image} sty={{ height: 180, }} />
</TouchableOpacity>
}
</View>
)}
/>
</View>
)
}
You can't really update or execute any JS code like that. I would suggest to go through few training to learn about how things work in React JSX.
For now, if you want to update the state then you should be good to use it in useEffect as shown below
export default function HomePage({ navigation }) {
const [parentServiceCheck, setParentServiceCheck] = useState({ check: '0' });
useEffect(() => {
const hasValidId = allService.find(_hasValidId);
// This will get called only once after first render
// This will execute only when id's are 5, 6 or 7 which is your requirement
if (hasValidId) {
setParentServiceChildCheck({ check: '1' });
}
}, []);
const _hasValidId = itemData => {
const itemId = itemData?.item?.id;
const validItemIds = [5, 6, 7];
return validItemIds.includes(itemId);
};
const _renderItem = itemData => {
const itemId = itemData?.item?.id;
let content = null;
if (_hasValidId(itemData)) {
if (parentServiceCheck.check === '0') {
content = (
<TouchableOpacity onPress={() => pressHandler(itemId)} style={{ margin: 7 }}>
<Card titlebtn="Other Things" src={itemData.item.service_image} sty={{ height: 180 }} />
</TouchableOpacity>
);
} else {
content = (
<TouchableOpacity onPress={() => pressHandler(itemId)} style={{ margin: 7 }}>
<Card
titlebtn={itemData.item.service_name}
src={itemData.item.service_image}
sty={{ height: 180 }}
/>
</TouchableOpacity>
);
}
}
return <View style={{ flexDirection: 'column', flex: 1 }}>{content}</View>;
};
return (
<View>
<FlatList
numColumns={2}
contentContainerStyle={{ margin: 7, flex: 1 }}
keyExtractor={(item, index) => item.id}
data={allService}
renderItem={_renderItem}
/>
</View>
);
}
FYI, you might need to change few lines as the whole component code is not shared and I don't know about your business logic.
You should never update state in return / render function. It will make infinite rendering loop.