I want to make the FlatList to Load more when scrolling to end also when I go back to top I need the items ready
I tried to use startAfter() method but it shows like it is not working with real time only with Firestore so the query not retrieve data when scrolling to bottom also when I use startAt() with the last key it shows error said: you should use a unique key.
see the code below :
componentDidMount = () => {
this.retrieveData();
}
retrieveData = async () => {
try {
let services = await firebase.database().ref('services').orderByKey().limitToLast(5)
services.on('value', snapshot => {
let arrayOfKeys = Object.values(snapshot.val())
referenceToOldestKey = arrayOfKeys[arrayOfKeys.length - 1].serviceKey;
this.setState({
dataSource: arrayOfKeys,
lastVisible: referenceToOldestKey,
});
});
}
retrieveMore = async () => {
try {
this.setState({
refreshing: true,
});
let services = await firebase.database().ref('services').orderByKey().endAt(this.state.lastVisible).limitToLast(6)
services.on('value', snapshot => {
let arrayOfKeys = Object.values(snapshot.val())
.sort()
.reverse()
.slice(1);
referenceToOldestKey = arrayOfKeys[arrayOfKeys.length - 1].serviceKey;
this.setState({
dataSource: arrayOfKeys,
lastVisible: referenceToOldestKey,
refreshing: false,
});
});
}
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.dataSource}
renderItem={({ item }) => (
<View style={styles.itemContainer}>
<Text>{item.serviceName}</Text>
</View>
)}
keyExtractor={(item) => item.serviceKey.toString()}
ListFooterComponent={this.renderFooter}
onEndReached={this.retrieveMore}
onEndReachedThreshold={0}
refreshing={this.state.refreshing}
/>
)
}
Related
in react native app,
i'm trying to get data from async function which will bring me back Promise<AlarmType[] | undefined>
Q1. so, in getAlarms.then() function, the undefined case is filtered and an empty array is printed in my console.
and after saving code in vscode, the console prints an array with proper data
Q2.the reason why i use useLayoutEffect and useEffect separately is
i just wanna separate the data fetching code from the react navigation header setOption code
but i'm not sure if it is a good practice
Is there any better ways to do this?
edit: i’m using react-native-simple-alarm
const [alarms, setAlarms] = useState<AlarmType[]>([]);
const fetchData = useCallback(() => {
getAlarms().then(response => {
if (response) setAlarms(response);
else console.log('undefined | empty array returned');
});
}, []);
useLayoutEffect(() => {
fetchData();
const willFocusSubscription = navigation.addListener('focus', () => {
fetchData();
});
console.log(alarms) // here, this function is called twice, and return empty array
return willFocusSubscription;
}, []);
useEffect(() => {
navigation.setOptions({
headerLeft: () => <Icon name="trash-can-outline" size={30}
onPress={() => {
deleteAllAlarms();
fetchData();
}}/>,
headerTitle: 'Alarm',
headerRight: () =><Icon name="plus" size={30} onPress={() => navigation.navigate('ModalStackView')}/>,
});
}, []);
in getAlarms.ts
export const getAlarms = async () => {
try {
return await RNGetAlarms();
} catch (error) {
console.log('setting call error' + error);
}
};
The useLayoutEffect is called before the render cycle of React which means before rendering the JSX content in your code this hook is being called.
So, If there is any requirement before JSX render like change header name, show header left or right buttons, etc.
and the useEffect is called after the initial render cycle is completed. when the JSX code is done with the rendering UI part.
So, I think your code should look like below:
const [alarms, setAlarms] = useState<AlarmType[]>([]);
const fetchData = useCallback(() => {
getAlarms().then(response => {
if (response) setAlarms(response);
else console.log('undefined | empty array returned');
});
}, []);
useEffect(() => {
const willFocusSubscription = navigation.addListener('focus', () => {
fetchData();
});
return willFocusSubscription;
}, [fetchData, navigation]);
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => <Icon name="trash-can-outline" size={30}
onPress={() => {
deleteAllAlarms();
fetchData();
}}/>,
headerTitle: 'Alarm',
headerRight: () =><Icon name="plus" size={30} onPress={() => navigation.navigate('ModalStackView')}/>,
});
}, [deleteAllAlarms, fetchData, navigation]);
I am using Promise.all in order to fetch multiple apis.
const ListScreen = () => {
const first = fetch('https://EXAMPLEAPI').then(resp => resp.json())
const second = fetch('https://EXAMPLEAPI').then(resp => resp.json())
const third = fetch('https://EXAMPLEAPI').then(resp => resp.json())
const retrieveAll = async function () {
let results = await Promise.all([first, second, third])
When console.log(results), I get all arrays of objects from apis
The problem is that when I create a FlatList, I don't get anything to be rendered on the screen(blank)
const retrieveAll = async function () {
let results = await Promise.all([first, second, third])
return (
<FlatList
keyExtractor={item => item.title}
data={results}
renderItem={({ item }) => {
return <Text>{item.title}</Text>
}}
/>
)
};
}
export default ListScreen;
What am I doing wrong?
Please help. :(
You need to re-render the component, for that you will have to use react Hooks.
This is how the component will look like
const RetrieveAll = function () {
const [ results, setResults ] = useState([])
useEffect( () => {
Promise.all([first, second, third])
.then(response => {
setResults(response)
})
}, [])
return (
<FlatList
keyExtractor={item => item.title}
data={results}
renderItem={({ item }) => {
return <Text>{item.title}</Text>
}}
/>
)
};
Usage
<RetrieveAll />
And try not to create async JSX elements.
I just started learning React native, and want to render FlaList after setState.
I am try to call Api to get Data and then I sorting that data but the FlatList is not rerender with newData. I also try extraData but nothing happen. Where am I missing?
Thank for your help.
function HomeScreen(props) {
const {transactions = []} = useSelector(selectors.transactions) || [];
const [listTransaction, setListTransaction] = useState([]);
useEffect(() => {
dispatch(BalanceActions.balanceRequest()); // This is my call Api
sortListTransaction(); // I call sortFunc after that
}, []);
const sortListTransaction = () => { // In this function I group by date the array of the result Api
let groups = [];
transaction.forEach((item) => {
let date = moment(item.date).format('MM/DD/YYYY');
if (date in groups) {
groups[date].push(item);
} else {
groups[date] = new Array(item);
}
});
setListTransaction(groups);
};
const _renderItem = ({item}) => {
return <BodyContent data={item} />;
};
// Then my FlatList like:
return (
<FlatList
data={listTransaction}
keyExtractor={(item) => item.id}
renderItem={_renderItem}
extraData={listTransaction}
/>
)
}
I have a screen where I am using a query like this:
export const AllFriends: React.FunctionComponent = () => {
const navigation = useNavigation();
const { data, error } = useGetMyProfileQuery({
onCompleted: () => {
console.log('hellooo')
},
});
Every time I visit this page from the home-page, I think the query re-runs as I always see the hellolog. Similarly, from this page, I visit another screen like this:
<Text
onPress={() => navigation.navigate('PendingRequests')}>
Pending Requests (
{data
? data.me.pendingUserRelationsRequestsReceived.totalCount
: ''}
)
</Text>
Every time I visit this screen, I see the hellooo from pending again. This screen looks like this:
export const ReceivedPendingRequests: React.FunctionComponent = () => {
const navigation = useNavigation();
const { data, error } = useGetMyProfileQuery({
onCompleted: () => {
console.log('hellooo from pending')
},
});
return (
<SafeAreaView style={styles.safeView}>
<Container style={styles.container}>
<Text
style={styles.backText}
onPress={() => navigation.navigate('AllFriends')}>
Zurück
</Text>
</Container>
</SafeAreaView>
);
};
Now the problem is that when I navigate back to AllFriends, the query should re-run and I should see the hello log again just like I see when I come from the Homepage to AllFriends. But I don't.
However, if I come back from AllFriends to PendingRequests, I see the log hellooo from pending again.
Edit:
useFocusEffect(()=>{
getMyProfile()
},[]);
const getMyProfile = () => {
const { data, error } = useGetMyProfileQuery({
onCompleted: () => {
console.log('hellooo')
},
//fetchPolicy: 'network-only',
});
}
You have to call refetch it will re-run the query for you. You can pass it to other screens aswell.
You can get it like this:
const {loading, data, refetch} = useQuery(Query, {})
Now in you useFocuseEffect just call this:
useFocusEffect(()=>{
React.useCallback(() => {
refetch();
}, [])
},[]);
when using navigation.navigate the screen doesn't "unmount", so it will not be reinitialized as fresh from the start as the screen does when you're using navigation.replace
Try using navigation.replace instead of navigation.navigate. I hope it helps, if not, let me know.
I'm retrieving data from cloud firestore as an array of objects and I pass the object's values as props to another component:
renderTips() {
firebase.firestore().collection('pendingtips').get()
.then(doc => {
doc.forEach(tip => {
const tipData = tip.data();//array's object
console.log(tipData.tip); //prints tip as expected
console.log(tipData.name); //prints name as expected
return <PendingTip key={tipData.tip} name={tipData.name} tip={tipData.tip} />; //doesn't returning enything
});
})
.catch(() => Alert.alert('error'));
}
render() {
return (
<View style={styles.containerStyle}>
<ScrollView style={styles.tipsContainerStyle}>
{this.renderTips()}
</ScrollView>
</View>
);
}
The array of objects looks like this:
{ name: 'Danny', tip: 'Be careful when crossing the road' },
{ name: 'Alex', tip: 'Drink water' }
The expectation is that in my ScrollView I will have a list of "tips". instead, I get nothing back as if the values are not being passed to the component.
thanks in advance.
RenderTips returns a promise which means it won't return anything at first render but only when the promise resolves. You need setState in renderTips to tell react to re-render your component when data comes. Make a seperate state array object for pendingTips then add the pendingTips component to that array and call setState
this.state = { pendingTips: [] }
componentDidMount() {
let pendingTips = [] // declare an array
firebase.firestore().collection('pendingtips').get()
.then(doc => {
doc.forEach(tip => {
const tipData = tip.data();//array's object
pendingTips.push(<PendingTip key={tipData.tip} name={tipData.name} tip={tipData.tip} />); // push items in the array
});
this.setState({pendingTips})
})
.catch(() => Alert.alert('error'));
}
render() {
return (
<View style={styles.containerStyle}>
<ScrollView style={styles.tipsContainerStyle}>
{this.state.pendingTips.map(tips => tips)}
</ScrollView>
</View>
);
}
You can solve this issue by setting doc as a state property and by moving the function for getting data into either some lifecycle method or effect hook.
You can try something like this:
componentDidMount () {
firebase.firestore().collection('pendingtips').get()
.then(doc => {
this.setState({doc})
})
}
renderTips() {
const {doc} = this.state
return doc ? doc.map(tip => {
const tipData = tip.data();
return <PendingTip
key={tipData.tip}
name={tipData.name} tip={tipData.tip} />;
}) : null
}
render() {
return (
<View style={styles.containerStyle}>
<ScrollView style={styles.tipsContainerStyle}>
{this.renderTips()}
</ScrollView>
</View>
);
}