i am new to react native please bear with me. I currently have a flatlist in my react native project which fetch's posts from my backend using an api. I have success implemented pagination on my backend and in my app when i scroll to the end it adds the newly fetched posts to the bottom of the current posts shown.
My problem is when i scroll to refresh, i only want the posts of page 0 to get shown.
What is happening is the posts on page 0 get added to the bottom of the posts instead of showing these posts only.
So for example, when i load my app, posts on page 0 get rendered and when i scroll to refresh instead of just showing posts on page 0, the posts on page 0 get added to the bottom of the posts on page 0 and therefore i get an error that id is duplicated.
The required action i am trying to obtain is when i scroll to refresh i just want the posts on page 0 to show.
Here is my code:
function PostsScreen({ navigation }) {
const [posts, setPosts] = useState([]);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const loadPosts = async () => {
setLoading(true);
const response = await postsApi.getPosts(page);
setLoading(false);
if (!response.ok) return setError(true);
setError(false);
setPosts([...posts,...response.data]);
};
const[page,setPage]=useState(0);
useEffect(() => {
loadPosts(page);
}, [page]);
const handleLoadMore = ()=>{
console.log('loadmore')
setPage(page+1);
}
const [refreshing, setRefreshing] = useState(false);
return (
<>
<ActivityIndicator visible={loading} />
<Screen style={styles.screen}>
<FlatList
data={posts} // to have all the data
keyExtractor={(post) => post.id.toString()}
renderItem={({ item,index }) => (
<Card
title={item.title}
subTitle={item.subTitle}
onPress={() => navigation.navigate(routes.POST_DETAILS, {post:item,index})}
/>
)}
refreshing={refreshing}
onRefresh={() => {
loadPosts(0); // I think the problem is here.//
setPage(0);
}}
onEndReached={handleLoadMore}
onEndReachedThreshold={0}
initialNumToRender={10}
/>
</Screen>
</>
);
}
Thank you in advance.
From what I read, I believe you want to implement pagination in your application and you have that in your code as well. Now, when you try refreshing your list, you just want to set the list to the initial 10 items. if so,
function PostsScreen({ navigation }) {
const [posts, setPosts] = useState([]);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(0);
const [refreshing, setRefreshing] = useState(false);
const loadPosts = async () => {
setLoading(true);
const response = await postsApi.getPosts(page);
setLoading(false);
if (refreshing)
setRefreshing(false);
if (!response.ok) return setError(true);
setError(false);
if (page == 0)
setPosts(response.data)
else
setPosts([...posts, ...response.data]);
};
useEffect(() => {
loadPosts();
}, [page]);
const handleLoadMore = () => {
setPage(page + 1);
};
const onRefresh = () => {
setRefreshing(true);
setPage(0);
};
return (
<>
<ActivityIndicator visible={loading} />
<Screen style={styles.screen}>
<FlatList
data={posts}
renderItem={({ item, index }) => (
<Card
title={item.title}
subTitle={item.subTitle}
onPress={() => navigation.navigate(routes.POST_DETAILS, { post: item, index })}
/>
)}
refreshing={refreshing}
onRefresh={onRefresh}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.05}
keyExtractor={(post) => post.id.toString()}
/>
</Screen>
</>
);
}
This should fix your problem.
const loadPosts = async () => {
setLoading(true);
const response = await postsApi.getPosts(page);
setLoading(false);
if (!response.ok) return setError(true);
setError(false);
if(page=== 0){
setPosts(response.data);
}else{
setPosts([...posts,...response.data]);
}
};
//...
onRefresh={() => {
setPage(0);
loadPosts(); // loadPosts takes no argument.
}
}
//...
const handleLoadMore = ()=>{
console.log('loadmore')
setPage(page+1);
loadPosts();
}
Related
im trying to get repos in order of stars from the API, but as i scroll, i keep getting random duplicates :
export default function App() {
const [repos, setRepos] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(true);
useEffect(() => {
const getUsers = async () => {
const res = await axios.get(
`https://api.github.com/search/repositories?q=created:>2020-01-01&sort=stars&order=desc&page=${page}`
);
const data = await res.data;
// setRepos([...repos, ...data.items]);
setRepos([...repos, ...data.items]);
setLoading(false);
};
getUsers();
}, [page]);
const scrollToEnd = () => {
if (loading === false) {
setPage(page + 1);
setLoading(true);
}
console.log("page", page);
};
const renderItem = ({ item }) => (
<Text className="boxed">
Stars: {item.stargazers_count} Id: {item.id}
</Text>
);
return (
<SafeAreaView style={styles.screen}>
<View style={styles.container}>
<FlatList
data={repos}
renderItem={renderItem}
keyExtractor={(item) => item.id}
onEndReached={scrollToEnd}
showsVerticalScrollIndicator={false}
/>
{loading && <Text className="loading">...loading</Text>}
</View>
<StatusBar style="auto" />
</SafeAreaView>
);
}
You should avoid duplicate data before set it to state, update your getUsers function with this,
useEffect(() => {
const getUsers = async () => {
const res = await Axios.get(
`https://api.github.com/search/repositories?q=created:>2020-01-01&sort=stars&order=desc&page=${page}`,
);
const data = await res.data;
let arrRepos = [...repos, ...data.items];
let uniq = new Set(arrRepos.map((objRepo) => JSON.stringify(objRepo)));
arrRepos = Array.from(uniq).map((objRepo) => JSON.parse(objRepo));
setRepos(arrRepos);
setLoading(false);
};
getUsers();
}, [page]);
I think you got too much going on in your useEffect function, maybe missing a closing }. Try this:
function App() {
const [repos, setRepos] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(true);
// This will trigger when page changes:
useEffect(() => {
getUsers();
}, [page]);
const getUsers = async () => {
const res = await axios.get(
`https://api.github.com/search/repositories?q=created:>2020-01-01&sort=stars&order=desc&page=${page}`
);
const data = await res.data;
// setRepos([...repos, ...data.items]);
setRepos([...repos, ...data.items]);
setLoading(false);
};
const scrollToEnd = () => {
if (loading === false) {
setPage(page + 1);
setLoading(true);
}
console.log("page", page);
};
const renderItem = ({ item }) => (
<Text className="boxed">
Stars: {item.stargazers_count} Id: {item.id}
</Text>
);
return (
<SafeAreaView style={styles.screen}>
<View style={styles.container}>
<FlatList
data={repos}
renderItem={renderItem}
keyExtractor={(item) => item.id}
onEndReached={scrollToEnd}
showsVerticalScrollIndicator={false}
/>
{loading && <Text className="loading">...loading</Text>}
</View>
<StatusBar style="auto" />
</SafeAreaView>
);
}
export default App;
I am trying to pass an array to another component. Data is a json object array with data in it. I know the data is there because when i click the button, the handleclick method is called and the data is displayed but when i try to pass that data as an array (seasondisplay and seasonlength) I get an error: Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. How do i pass the data from the fetch as an array to the dropdown box component?
function HomeScreen({ navigation }) {
const [list, setList] = useState([]);
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
let seasons = [];
useEffect(() => {
fetch('http://localhost:3000/person', {
credentials: "same-origin"
})
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => console.log("junk" + error))
.finally(() => setLoading(false));
}, []);
const [display, setDisplay] = useState("");
const handleClick = () => {
setDisplay(
data.map((item, i) => (
<Text key={i}> {item.lastname} </Text>
)))
}
const [seasondisplay, setSeasonDisplay] = useState("");
const [seasonlength, setSeasonLengthDisplay] = useState(0);
setSeasonDisplay(
data["lastname"]
)
setSeasonLengthDisplay (
2
)
return (
<View style={styles.container}>
<View style={styles.innerContainer}>
<DropDownChoice whichDropDown="Season" HowMany={seasonlength} ItemChoices={seasondisplay} />
<DropDownChoice whichDropDown="Veggies" HowMany={4} ItemChoices={["carrots", "turnip", "peas", "corn"]} />
<DropDownChoice whichDropDown="Fruit" HowMany={3} ItemChoices={["apples", "oranges", "bananas"]} />
<Button
title="Connect to DB"
onPress={() => {
console.log("please work");
console.log(data);
handleClick();
}}
here is json:
[{"personid":11,"lastname":"cook","firstname":"ben","address":"north","city":"london"},{"personid":22,"lastname":"smith","firstname":"elaine","address":"main","city":"milton"}]
I suspect that:
You are changing state on each render by setSeasonDisplay. This state change cause re render and again in rendering you changing state. You have a loop.
function HomeScreen({ navigation }) {
const [list, setList] = useState([]);
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
let seasons = [];
useEffect(() => {
fetch('http://localhost:3000/person', {
credentials: "same-origin"
})
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => console.log("junk" + error))
.finally(() => setLoading(false));
}, []);
const [display, setDisplay] = useState("");
const handleClick = () => {
setDisplay(
data.map((item, i) => (
<Text key={i}> {item.lastname} </Text>
)))
}
const [seasondisplay, setSeasonDisplay] = useState("");
const [seasonlength, setSeasonLengthDisplay] = useState(0);
//---------- changes
useEffect(() => {
setSeasonDisplay(data["lastname"])
setSeasonLengthDisplay (2)
}, [data])
//----------------
return (
<View style={styles.container}>
<View style={styles.innerContainer}>
<DropDownChoice whichDropDown="Season" HowMany={seasonlength} ItemChoices={seasondisplay} />
<DropDownChoice whichDropDown="Veggies" HowMany={4} ItemChoices={["carrots", "turnip", "peas", "corn"]} />
<DropDownChoice whichDropDown="Fruit" HowMany={3} ItemChoices={["apples", "oranges", "bananas"]} />
<Button
title="Connect to DB"
onPress={() => {
console.log("please work");
console.log(data);
handleClick();
}}
I have a function to fetch items from an API that is inside UseEffect. And i'm looking to call this function every time the status of the selectedItem or the items changes and show an ActivityIndicator before the function returns the result. The ActivityIndicator appears when the items are uploading but not when the status of the selectedItem changes ?
I have my code like this :
export default () => {
const [items, setItems] = useState();
const [selectedItem, setSelectedItem] = useState(null);
const [isLoading, setLoading] = useState(true);
const getItems = () => {
get('/api/items').then((rep) => {
setItems(rep);
setLoading(false);
}
});
};
useEffect(() => {
getItems();
}, [selectedItem.status]);
return (
<SafeAreaView style={styles.container}>
{isLoading ? (
<View style={[styles.spinnerContainer, styles.horizontal]}>
<ActivityIndicator />
</View>
) : ((items !== [])
&& (
<SectionList
stickySectionHeadersEnabled={false}
style={{ paddingHorizontal: 20, }}
sections={items}
refreshing={isLoading}
keyExtractor={(item, index) => item + index}
...
/>
))}
</SafeAreaView>
);
};
You can try setLoading(true) inside getItems
const getItems = () => {
setLoading(true);
get('/api/items').then((rep) => {
setItems(rep);
setLoading(false);
});
};
I'm trying to access a screen when you click on an item in my flatlist by passing the date I retrieved from the firebase before, I've tried several things without success so I come to you.
Basically when I click on one of the elements -> A screen with details should appear.
export default function Notifications() {
const dbh = firebase.firestore();
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [deliveries, setDeliveries] = useState([]); // Initial empty array of users
useEffect(() => {
const subscriber = dbh
.collection("deliveries")
.onSnapshot((querySnapshot) => {
const deliveries = [];
querySnapshot.forEach((documentSnapshot) => {
deliveries.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setDeliveries(deliveries);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
style={{ flex: 1 }}
data={deliveries}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => { * HERE I NEED TO PASS DATA AND SHOW AN ANOTHER SCREEN FOR DETAILS * }}>
<View style={styles.container}>
<Text>DATE: {item.when}</Text>
<Text>ZIP DONATEUR: {item.zip_donator}</Text>
<Text>ZIP BENEFICIAIRE: {item.zip_tob_deliv}</Text>
</View>
</TouchableOpacity>
)}
/>
);
}
EDIT: Small precision this screen is located in a Tab.Navigator
you can pass params in navigation,
export default function Notifications(props) {
const { navigation } = props
const dbh = firebase.firestore();
const [loading, setLoading] = useState(true); // Set loading to true on component mount
const [deliveries, setDeliveries] = useState([]); // Initial empty array of users
useEffect(() => {
const subscriber = dbh
.collection("deliveries")
.onSnapshot((querySnapshot) => {
const deliveries = [];
querySnapshot.forEach((documentSnapshot) => {
deliveries.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setDeliveries(deliveries);
setLoading(false);
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
if (loading) {
return <ActivityIndicator />;
}
return (
<FlatList
style={{ flex: 1 }}
data={deliveries}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => {
navigation.navigate('screenName', {
//pass params here
})
}}>
<View style={styles.container}>
<Text>DATE: {item.when}</Text>
<Text>ZIP DONATEUR: {item.zip_donator}</Text>
<Text>ZIP BENEFICIAIRE: {item.zip_tob_deliv}</Text>
</View>
</TouchableOpacity>
)}
/>
);
}
you can access params in the navigated screen by props.route.params
In my code, for the first time, loadAlbums method runs correctly. Why I scroll down retrieveMore function calls several times.
const PAGE_SIZE = 15;
const App = () => {
const [albums, setAlbums] = useState([]);
const [page, setPage] = useState(1);
const loadAlbums = async () => {
const response = await axios.get(`http://xxx:3000/posts/${PAGE_SIZE}/${page}`);
setAlbums([...albums, ...response.data]);
setPage(page + 1)
}
useEffect(() => {
loadAlbums();
}, [])
const renderRowItem = ({ item }) => {
return (
<View style={styles.item}>
<Text>{item.id}</Text>
<Text>{item.body}</Text>
</View>
)
}
const retrieveMore = useCallback(() => {
loadAlbums(); // this line calls several times
})
return (
<View style={styles.screen}>
<FlatList
data={albums}
renderItem={renderRowItem}
keyExtractor={(item, index) => index.toString()}
onEndReached={retrieveMore}
onEndReachedThreshold={0.5}
/>
</View>
)
}
Just Reduce threshold
onEndReachedThreshold={0.5} to onEndReachedThreshold={0.2}
Currently what's happening here every time you reach 50% of the screen the api will be invoked.