I'm building an app with React native. I'm using a FlatList with an
onRefresh handler:
<FlatList
data={data}
renderItem={renderPost}
keyExtractor={(item, index) => index.toString()}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={renderFooter}
refreshing={isRefreshing}
onRefresh={handleRefresh}>
</FlatList>
Within that onRefresh handler I reset the data list and fetch new data:
const handleRefresh = () => {
setData([]);
setIsRefreshing(true);
fetchData();
}
The problem is that data is never set to []. I can read here that it's expected behaviour:
useState set method not reflecting change immediately.
But what would be a better way? Because when I use, useEffect I have the same problem:
useEffect(() => {
setData([])
fetchData();
}, [isRefreshing]);
const handleRefresh = () => {
setIsRefreshing(true);
}
isRefreshing is never set to true.
What is the best way of tackling this?
--EDIT
fethData method:
const fetchData = () => {
const url = 'my-api-url.com?page=' + page;
fetch(url, {
method: 'GET',
}).then((response) => response.json())
.then((json) => {
setData(data.concat(json.data));
setIsLoading(false);
setIsRefreshing(false);
});
}
If you get what I'm trying to do here it might work best for you
// how about isolating all the data fetch related hooks
// fetch will be called anytime your request params updates
// qs is from query string library
const useDataFetch = (url, method, params) => {
const [refreshing, setRefreshing] = useState(false)
const [fetching, setFetching] = useState(false)
const [data, setData] = useState([])
useEffect(() => {
async (() => {
const url = `${url}?${qs.stringify(params)}`
// we set fetching to true while data is still to be fetched
await setFetching(true)
const rawResponse = await fetch(url, {method})
// and set it back to false when done
const newData = rawResponse.json().data
if (refreshing) {
setData(newData)
setRefreshing(false)
} else {
setData([...data, ...newData])
}
setFetching(false)
})()
}, [params])
return {refreshing, setRefreshing, fetching, data}
}
// and use it like this
// only params is outside of useDataFetch because of the feature refreshing
export default myApp = () => {
const [params, setParams] = useState({page: 1})
const {refreshing, setRefreshing, fetching, data} = useDataFetch('my-api-url.com', 'GET', params)
const handleRefresh = async () => {
await setRefreshing(true)
setParams({page: 1})
}
return (
<FlatList
data={data}
renderItem={renderPost}
keyExtractor={(item, index) => index.toString()}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={renderFooter}
refreshing={refreshing}
onRefresh={handleRefresh}>
</FlatList>
)
}
// now things are reuseable and less code from now on
Related
I am using a database with MySQL and getting it using Axios and a useEffect. Then I pass my database data to a component using a prop. Like this:
const Component = () => {
//DB
const urlProxy = "/api/cities";
//Hooks
const [data, setData] = useState([]);
//DB Fetch
const fetchData = async () => {
await axios
.get(urlProxy)
.then((res) => {
setData(res.data);
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
return () => {
fetchData();
};
}, []);
return (
<>
<h1>Cities</h1>
<Cities api={data} />
</>
);
};
Inside of Cities Component I want to make an algorithm to manipulate that data, but I get one empty array (from const [data, setData] = useState([]). After a moment I get the fetched data from database.
const Cities = (api) => {
console.log(data) // First Print: [{api:Array(0)}] then [{api:Array(2)}]
return(
<>
...
</>
)
}
So if it prints at first an empty array I would get an error
I was thinking of using a useTimeout() but i don't know if there is a better solution, in order to use data after it's fetched.
All you would need to do is manipluate the data before you set it into your state
and the best way to wait until that is done is to have a loading state that waits for your data to be pulled and then have your useEffect manipulate it.
Something like this should work for you
const urlProxy = "/api/cities";
const Component = () => {
const [data, setData] = useState();
const [loading, setLoading] = useState(true);
//DB Fetch
const fetchData = async () => {
await axios
.get(urlProxy)
.then((res) => {
// Manipulate your data here before you set it into state
const manipluatedData = manipulateData(res.data)
setData(manipluatedData);
})
.catch((err) => {
console.log(err);
})
.finally(() =>
setLoading(false);
})
};
useEffect(() => {
return () => {
fetchData();
};
}, []);
if(loading){
return 'loading....'
}
return (
<>
<h1>Cities</h1>
<Cities api={data} />
</>
);
};
I am trying to let the FlatList get 20 posts from Firestore and render 20. when the end is reached I would like to call the getPosts method to get the next 20 posts which means I will have to have a way to save the last known cursor. This is what I was trying to do when converting class component to hooks.
Please can someone help me , no one answered my last question about this
const Posts = (props) => {
//How to get 20 posts from firebase and then render 20 more when the end is reached
const [allPosts, setAllPosts] = useState();
const [loading, setLoading] = useState(true)
const [isRefreshing, setRefreshing] = useState(false);
useEffect(() => {
getPosts();
}, []);
const getPosts = async () => {
try {
var all = [];
const unsubscribe = await firebase
.firestore()
.collection("Posts")
.orderBy("timestamp",'desc')
.get()
.then((querySnapshot) => {
querySnapshot.docs.forEach((doc) => {
all.push(doc.data());
});
setLoading(false);
});
setAllPosts(all);
if(currentUser === null){
unsubscribe()
}
} catch (err) {
setLoading(false);
}
};
const onRefresh = useCallback(() => {
setRefreshing(true);
getPosts()
.then(() => {
setRefreshing(false);
})
.catch((error) => {
setRefreshing(false); // false isRefreshing flag for disable pull to refresh
Alert.alert("An error occured", "Please try again later");
});
}, []);
return (
<FlatList
data={allRecipes}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={onRefresh}
/>
}
initialNumToRender={20}
keyExtractor={(item, index) => item.postId}
renderItem={renderItem}
/>
);
}
const Posts = () =>{
const [posts, setPosts] = useState();
const [data, setData] = useState();
const addPosts = posts => {
setData({...data,...posts})
// `setData` is async , use posts directly
setPosts(Object.values(posts).sort((a, b) => a.timestamp < b.timestamp))
};
}
You need to add a scroll event listener here
something like:
const Posts = (props) => {
useEffect(() => {
window.addEventListener('scroll', () => {
if (window.scrollY >= (document.body.offsetHeight + window.innerHeight)) {
// fetch more posts here
}
});
});
// ...rest of the codes
}
I am trying to get data from my firebase-firestore I an showing a loading state to wait for the data to load however when it does load it keeps returning the firestore data infinite times. Please may someone help me.
This is my code Paper is just a custom component
import Paper from '../Components/Paper'
import firebase from 'firebase'
import { useState } from 'react'
const Home = (props) => {
const renderMealItem = (itemData) =>{
return (
<Paper
title={itemData.item.name}
serves={itemData.item.servings}
time={itemData.item.time}
image={itemData.item.imageUri}
/>
)
}
const [loading, setLoading] = useState(false)
const [all, setAll] = useState([])
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
},[])
const checkReturn = () => {
if(all !== undefined){
setLoading(false)
}
}
const getUser = async() => {
try {
await firebase.firestore()
.collection('Home')
.get()
.then(querySnapshot => {
querySnapshot.docs.forEach(doc => {
setAll(JSON.stringify(doc.data()));
});
});
}catch(err){
console.log(err)
}
}
return(
<View style={styles.flatContainer}>
<FlatList
data={all}
keyExtractor={(item, index) => index.toString()}
renderItem={renderMealItem}/>
</View>
)
}
useEffect without second parameter will get executes on each update.
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
})
so this will set the loading and tries to get the user. and when the data comess from server, it will get runned again.
So you should change it to : useEffect(() => {...}, []) to only get executed on mount phase(start).
Update: you should check for return on every update, not only at start. so change the code to:
useEffect(() => {
setLoading(true)
getUser()
}, [])
useEffect(() => {
checkReturn()
})
Ps: there is another issue with your code as well:
querySnapshot.docs.forEach(doc => {
setAll(JSON.stringify(doc.data()));
});
maybe it should be like :
setAll(querySnapshot.docs.map(doc => JSON.stringify(doc.data())));
Try passing an empty array as an argument to useEffect like so:
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
}, [])
I am trying to get an array of objects from my Redux-Store state called user and save it to async storage and use useState with the response to set the state before I retrieve it and view it with the FlatList however I am getting an error along the lines of Warning: Can't perform a React state update on an unmounted component. The user details is being set to the redux store in another component and then being retrieved from the current component I am displaying. Please could I get your help . I would really appreciate it. Thank you in advance!!!
const TheUser = (props) => {
//user is an array from redux store
const user = useSelector(state => state.account.cookbook)
const [getUser, setGetUser] = useState()
const saveUserAsync = async () => {
await AsyncStorage.setItem('user', JSON.stringify(user))
}
saveUserAsync()
AsyncStorage.getItem('user').then(response => {
setGetUser(response)
})
return (
<FlatList
data={getUser}
keyExtractor={item => item.id}
renderItem={itemData =>
<MyUser
name={itemData.item.name}
image={itemData.item.imageUri}
details={itemData.item.details.val}
/>
}
/>
)
}
export default TheUser
You can use useEffect hook to solve this problem.
IS_MOUNTED variable will track if component is mounted or not.
let IS_MOUNTED = false; // global value
const TheUser = (props) => {
//user is an array from redux store
const user = useSelector(state => state.account.cookbook)
const [getUser, setGetUser] = useState()
const saveUserAsync = async () => {
await AsyncStorage.setItem('user', JSON.stringify(user))
}
AsyncStorage.getItem('user').then(response => {
if(IS_MOUNTED)
{
setGetUser(JSON.parse(response));
}
});
useEffect(() => {
IS_MOUNTED = true;
saveUserAsync();
return (() => {
IS_MOUNTED = false;
})
},[])
return (
<FlatList
data={getUser}
keyExtractor={item => item.id}
renderItem={itemData =>
<MyUser
name={itemData.item.name}
image={itemData.item.imageUri}
details={itemData.item.details.val}
/>
}
/>
)
}
export default TheUser
import { useEffect } from "react"
let isMount = true
const TheUser = (props) => {
//user is an array from redux store
const user = useSelector(state => state.account.cookbook)
// const [getUser, setGetUser] = useState()
// useEffect(() => {
// const saveUserAsync = async () => {
// await AsyncStorage.setItem('user', JSON.stringify(user))
// const response = await AsyncStorage.getItem('user')
// if (isMount)
// setGetUser(JSON.parse(response))
// }
// saveUserAsync()
// }, [user])
// useEffect(() => {
// isMount = true
// return () => {
// isMount = false
// }
// }, [])
return (
<FlatList
// data={getUser}
data={user}
keyExtractor={item => item.id}
renderItem={itemData =>
<MyUser
name={itemData.item.name}
image={itemData.item.imageUri}
details={itemData.item.details.val}
/>
}
/>
)
}
export default TheUser
im using this Api to get json data.
const FetchEarthquakeData = url => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(jsonData => setData(jsonData.features))
}, [url]);
return data;
};
The problem is when I use this function like this:
const jsonData = FetchEarthquakeData(url)
console.log(jsonData);
I get following console.logs:
null
Array(17)
So my function FetchEarthquakeData returns the null variable and! the desired api. However if I want to map() over the jsonData, the null value gets mapped. How can I refactor my code so I get only the Array?
I'm not quite sure what useState() and setData() do. But in order to fetch the json data from the API, you can make the function as followed, then you can perform operations on the fetched data.
const url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson"
const FetchEarthquakeData = url => {
return new Promise(resolve => {
fetch(url)
.then(res => res.json())
.then(jsonData => resolve(jsonData.features))
})
}
FetchEarthquakeData(url).then(features => {
console.log(features)
// Do your map() here
})
as per the documentation of react hooks ,Only Call Hooks from React Functions Don’t call Hooks from regular JavaScript functions.
React Function Components -- which are basically just JavaScript Functions being React Components which return JSX (React's Syntax.)
for your requirement you can do as follows in your react component.
idea is to have state hook initialized with empty array then update it once json data available. the fetch logic can be moved inside useeffect .
const SampleComponent=()=>{
const [data,setData] = useState([])
useeffect (){
fetch(url).then((responsedata)=>setData(responseData) ,err=>console.log (err)
}
return (
<>
{
data.map((value,index)=>
<div key=index>{value}<div>
}
</>
)
}
if you find above fetching pattern repetitive in your app thou can use a custom hook for the same.
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error };
};
in your React component you can use like
const {data,error} = useFetch(url , options)
You have to do it in an async fashion in order to achieve what you need.
I recommend you doing it separately. In case you need the data to load when the component mounts.
Another thing is that your function is a bit confusing.
Let's assume some things:
const Example = () => {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await fetch(url);
setData(result.features);
};
fetchData();
}, []);
return (
<div>
{console.log(data)}
{data && <p>{JSON.stringify(data, null, 2)}</p>}
</div>
);
};
I am sure that the way you are doing it is storing the data properly on data, but you can not see it on your console.log. It is a bit tricky.
You can read a bit more here => https://medium.com/#wereHamster/beware-react-setstate-is-asynchronous-ce87ef1a9cf3
Adding a bit more of complexity in case you want to handle different states like loading and error.
const Example = () => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await fetch(url);
setData(result.features);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, []);
return (
<div>
{console.log(data)}
{data && <p>{JSON.stringify(data, null, 2)}</p>}
</div>
);
};