Suggestion for fetch the data in database and set progress bar - react-native

I have already stored the student id and number of books read in the database. now, I have implemented the method to get the details of books count read by the students. but the function progressbardata() doesn't return the data fetched from query. I have tried to assign a value manually inside the db.transaction() and returned it but no luck.
Could you please let me know where i am doing wrong. also this method looping multiple times. how can we sort this out.
import * as Progress from 'react-native-progress';
import { openDatabase } from 'react-native-sqlite-storage';
var db = openDatabase({ name: 'UserDatabase.db' });
let progressbardata = (id) => {
var totalItems=0;
db.transaction((tx) => {
tx.executeSql('SELECT * FROM student where id = ?', [id], (tx, results) => {
totalItems = results.rows.item(0).percent;
return (totalItems);
})
})
}
const _renderContent = (item, index) => {
return (
<View>
<Progress.Bar color='#68FF33' progress={progressbardata(item.id)}/>
</View>
)
}

My guess would be that the request to fetch the data is asynchronous and this code does not seem to wait for a response to continue. Therefor the code would continue while the fetch requests has not returned a value yet. If this is the case, I would suggest that you let the request set the State of the component, and make the state the input of the Progress.Bar component. This way, when the fetch requests finishes asynchronously, the state will be set and the progress bar will be updated. This would look something like this:
import * as Progress from 'react-native-progress';
import { openDatabase } from 'react-native-sqlite-storage';
import { useState } from 'react'
var db = openDatabase({ name: 'UserDatabase.db' });
const renderContent = (props) => {
[progress, setProgress] = useState(0)
let progressbardata = (id) => {
var totalItems=0;
db.transaction((tx) => {
tx.executeSql('SELECT * FROM student where id = ?', [id], (tx, results) => {
totalItems = results.rows.item(0).percent;
setProgress(totalItems);
})
})
}
progressbardata(props.id)
return (
<View>
<Progress.Bar color='#68FF33' progress={progress}/>
</View>
)
}

Related

How exactly do I update FlatList's data using Apollo Client's fetchMore method?

My GraphQL backend returns data in the form:
{
data: {
feed: {
A: [...],
B: [...]
}
}
}
Where my frontend merges A and B using a sort on some field present within the data, and then uses React Native's FlatList to display them as a single array. I'm using Apollo client to query my backend like so:
const { fetchMore, loading, data, error } = useQuery(fetchQuery, {variables: fetchArgs});
if (loading) {...}
if (error) {...}
let A = [];
let B = [];
if (data!.feed.A) A = data!.feed.A
if (data!.feed.B) B = data!.feed.B
let feedData = sortedMerge(A,B);
return (
<FlatList
data={feedData}
...
onEndReached{
//update fetchArgs
fetchMore({
variables: fetchArgs,
updateQuery: // Not sure if I need to do anything here?
}).then(result => {
// Maybe this is where I update?
})
}
)
However, I can't seem to figure out how or where to get the new data, say A' and B' merged and concatenated with feedData so that my FlatList can update as efficiently as possible?
I guess I'm having trouble because I can't just directly update the list and need to do a little bit of preprocessing, but no matter where I update feedData, either in the then block after fetchMore or outside of it, the FlatList never seems to update.
Try putting your merge logic in a useMemo:
const { fetchMore, loading, data, error } = useQuery(fetchQuery, {variables: fetchArgs});
const feedData = useMemo(() => {
if (data) {
const { A, B } = data.feed;
return sortedMerge(A,B);
} else return [];
},[data]);
if (loading) {...}
if (error) {...}
return (
<FlatList
data={feedData}
...
onEndReached = {() => fetchMore({ variables: fetchArgs })}
/>
)
Executing fetchMore should cause data to be updated which will trigger the useMemo and update your feedData variable.
However you'll still need to merge the paginated results into the client-side cache.

Prevstate in reactJs

This might be a silly question, but how do I get data if I saved the state using prevState.
I am trying to retrieve data from database and send that data through navigation.
const [dataFromDatabase, setDataFromDatabase] = useState('');
const retrieveFromDatabase = () => {
db.transaction(
tx => {
tx.executeSql("SELECT * FROM PreLoveeedTable",
[],
(_, { rows }) => {
console.log("ROWS RETRIEVED");
// clear data currently stored
setDataFromDatabase('');
let entries = rows._array;
entries.forEach((entry) => {
setDataFromDatabase(prev => prev + `${entry.id}, ${entry.image}, ${entry.title}, ${entry.price}, ${entry.description}\n
});
},
(_, result) => {
console.log('SELECT failed!');
console.log(result);
}
)
}
);
}
{dataFromDatabase} will give me the whole entire data in the database.
But wanted to have each entry in the Database. For example entry for title.
I have been stuck on this for a while now and would be appreciated it if i can get a hint.

Display date from Firestore React native

I try to display the picked event date för my posts from Firestore using moment. Right now it just print The current day, so I display the date but not ont he right way. I get no error message. I have tried to change the timestamp to "dateUpload". but it gives me a print in my Text component "invalid date". Do any have clue of what I can do?
This is how display it my flatlist:
postDate={moment(item.timestamp).format("ll")}
In my redux Action.js
export function fetchFollowingUsersPosts(uid) {
return ((dispatch, getState) => {
firebase.firestore()
.collection("posts")
.doc(uid)
.collection("userPosts")
.orderBy("creation", "asc")
.get()
.then((snapshot) => {
const uid = snapshot.query.EP.path.segments[1];
const user = getState().usersState.users.find(el => el.uid === uid);
const posts = snapshot.docs.map((doc) => {
const { data: firebaseTimestamp, ...rest } = doc.data()
const id = doc.id;
const data = firebaseTimestamp ? moment(firebaseTimestamp.toDate()) : null
return {
...rest,
id,
user,
...data
}
})
//console.log(posts);
dispatch({ type: USERS_POSTS_STATE_CHANGE, posts, uid, })
})
})
}
Image on my database:
Instead of const data = firebaseTimestamp ? moment(firebaseTimestamp.toDate()) : null, try this const data = firebaseTimestamp ? firebaseTimestamp.toDate() : null , so you'll be having a javascript Date object, instead of a Moment object.
Then, you can use it as you did postDate={moment(item.timestamp).format("ll")}, assuming that item.timestamp is the Date object from above

When state changes for graphql variable, result stays the same on react-native

I'm trying to create an app using shopify graphql api to create an ecommerce app on react native expo.
I have an onPress that calls a setState to change the state of the graphQL variable but the results don't change from the initial state of 'currentSubCategories'
const [currentSubCategories, setSubCategories] = useState(Categories[0].subCategory[0].handle);
let {
collection,
loading,
hasMore,
refetch,
isFetchingMore,
} = useCollectionQuery(currentSubCategories, first, priceRange);
const [currentCategory, setCategory] = useState({categories: Categories[0]});
const onSubCategorySelect = (subCategory) => { setSubCategories(subCategory.handle) }
onPress={() => onSubCategorySelect(item)}
function useCollectionQuery(
collectionHandle: string,
first: number,
priceRange: [number, number],
) {
let [isInitFetching, setInitFetching] = useState<boolean>(true);
let [isReloading, setIsReloading] = useState<boolean>(true);
let [collection, setCollection] = useState<Array<Product>>([]);
let isFetchingMore = useRef<boolean>(false);
let hasMore = useRef<boolean>(true);
let defaultCurrency = useDefaultCurrency().data;
let { data, loading, refetch: refetchQuery } = useQuery<
GetCollection,
GetCollectionVariables
>(GET_COLLECTION, {
variables: {
collectionHandle,
first,
sortKey: ProductCollectionSortKeys.BEST_SELLING,
presentmentCurrencies: [defaultCurrency],
},
notifyOnNetworkStatusChange: true,
fetchPolicy: 'no-cache',
});
let getMoreUntilTarget = async (
targetAmount: number,
cursor: string | null,
handle: string,
filter: [number, number],
) => {
let result: Array<Product> = [];
let moreData: Array<Product> = [];
let { data } = await refetchQuery({
first,
collectionHandle: handle,
after: cursor,
});
...
useEffect(() => {
if (!loading) {
isFetchingMore.current = false;
}
if (isInitFetching && !!data && !!data.collectionByHandle) {
let newCollection = mapToProducts(data.collectionByHandle.products);
hasMore.current = !!data.collectionByHandle?.products.pageInfo
.hasNextPage;
setCollection(newCollection);
setIsReloading(false);
setInitFetching(false);
}
}, [loading, isInitFetching]); // eslint-disable-line react-hooks/exhaustive-deps
return {
collection,
loading: isReloading,
hasMore: hasMore.current,
isFetchingMore: isFetchingMore.current,
refetch,
};
}
I'm using flatList to show the result
<FlatList
data={collection}
renderItem={({ item }) => (
<Text>{item.title}</Text>
)}
/>
According to docs you have to pass new variables to refetch otherwise refetch will use initial values.
In this case (custom hook) you have 2 ways to solvethis problem:
return variables from your custom hook (taken from useQuery),
return some own refetch function.
1st option needs 'manual' variables updating like:
refetch( { ...variablesFromHook, collectionHandle: currentSubCategories } );
In 2nd case you can create myRefetch (and return as refetch) taking collectionHandle parameter to call refetch with updated variables - hiding 'complexity' inside your hook.
Both cases needs refetch call after updating state (setSubCategories) so you should use this refetch inside useEffect with [currentSubCategories] dependency ... or simply don't use state, call refetch directly from event handler (in onSubCategorySelect).

React Native ListView - rowHasChanged doesn't fire

I am trying to implement an infinite scroll in React Native. Below is the source of the component:
var React = require('react-native');
var server = require('../server');
var Post = require('./Post');
var SwipeRefreshLayoutAndroid = require('./SwipeRefreshLayout');
var backEvent = null;
var lastPostId = "";
var isLoadingMore = false;
var isLoadingTop = false;
var onEndReachedActive = false;
var {
StyleSheet,
ListView,
View,
Text,
Image,
ProgressBarAndroid,
BackAndroid
} = React;
class Stream extends React.Component {
constructor(props) {
super(props);
this.ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => {
console.log("rowHasChenged FIRED!!");
return false;
}
});
this.state = {
dataSource: this.ds.cloneWithRows(['loader']),
hasStream: false,
posts: []
};
}
componentDidMount() {
BackAndroid.addEventListener('hardwareBackPress', () => {
this.props.navigator.jumpBack();
return true;
}.bind(this));
server.getStream('', '', 15).then((res) => {
lastPostId = res[res.length-1].m._id;
this.setState({
posts: res,
hasStream: true,
dataSource: this.ds.cloneWithRows(res)
}, () => onEndReachedActive = true);
})
}
onRefresh() {
var posts = this.state.posts;
var firstPost = posts[0].m._id;
console.log(this.state.dataSource._rowHasChanged);
isLoadingTop = true;
server.getStream('', firstPost, 4000)
.then(res => {
console.log(posts.length);
posts = res.concat(posts);
console.log(posts.length);
this.setState({
dataSource: this.ds.cloneWithRows(posts),
posts
}, () => {
this.swipeRefreshLayout && this.swipeRefreshLayout.finishRefresh();
isLoadingTop = false;
});
}).catch((err) => {
isLoadingTop = false;
})
}
onEndReached(event) {
if(!onEndReachedActive) return;
if(this.state.loadingMore || this.state.isLoadingTop)return;
isLoadingMore = true;
var posts = this.state.posts;
server.getStream(posts[posts.length-1].m._id, '', 15)
.then(res => {
console.log('received posts');
posts = posts.concat(res);
lastPostId = posts[posts.length-1].m._id;
this.setState({
dataSource: this.ds.cloneWithRows(posts),
posts
}, ()=>isLoadingMore = false);
})
}
renderHeader() {
return (
<View style={styles.header}>
<Text style={styles.headerText}>Header</Text>
</View>
)
}
renderRow(post) {
if(post === 'loader') {
return (
<ProgressBarAndroid
styleAttr="Large"
style={styles.spinnerBottom}/>
)
}
let hasLoader = post.m._id === lastPostId;
let loader = hasLoader ?
<ProgressBarAndroid
styleAttr="Large"
style={styles.spinnerBottom}/> : null;
return (
<View>
<Post
post={post}/>
{loader}
</View>
)
}
render() {
return (
<ListView
style={styles.mainContainer}
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
onEndReached={this.onEndReached.bind(this)}
onEndReachedThreshold={1}
pageSize={15} />
);
}
}
The problem is that whenever I append (or prepend) new data, the rowHasChanged method of the DataSource doesn't fire. It just re-renders every row, even tho nothing has changed (except the new data).
Any idea why the method is bypassed?
Edit: Pass a function to setState to avoid race conditions
I just figured it out. If you are having the same issue, check the point at which you change your state with the new dataSource. Mine was like this:
this.setState({
dataSource: this.ds.cloneWithRows(posts)
});
Instead you should always use the dataSource from the previous state, like this:
this.setState(state => ({
dataSource: state.dataSource.cloneWithRows(posts)
}))
Cheers!
this worked for me, hope this helps. I created a new dataSource and assigned the updated data to it on state change as follows:`
var dataSource = new ListView.DataSource(
{rowHasChanged: (r1, r2) => ( r1 !== r2)});
this.setState({ dataSource : dataSource.cloneWithRows(posts) });
Now, the new data is assigned and the view is rendered correctly. Note that posts array that is assigned now holds the updated data. Still wondering though if it's the best way to do it but it works!
I agree it seems to make sense that you should always use the dataSource from the previous state.
Yet when I setState this way, rowHasChanged gets called for all rows, however, rowHasChanged always returns false and no rows are rendered??? Why?
// This is callback handler that the ListView DetailView will
// call when a ListView item is edited
onChange(waypoint: Object){
console.log('Callback: rowNumber= ', waypoint.rowNumber);
console.log(' length(m)= ', waypoint.distance.meters);
var itemListChanged = this.state.itemList;
itemListChanged[waypoint.rowNumber-1] = waypoint;
this.setState({
dataSource: this.state.dataSource.cloneWithRows(itemListChanged),
});
},
If I setState this way, renderRow is called for all rows unconditionally without rowHasChanged ever being called. Which is correct?
this.setState({
dataSource: ds.cloneWithRows(itemListChanged),
});
ListView, datasource, and react-native are a hard learning curve coming from C#/C/C++.
for anyone still having issue with rowHasChanged called but are still returning false the following snippets might help
the datasource is initialized like usual:
let ds = new ListView.DataSource ({
rowHasChanged: (a, b) => {
const changed = (a !== b)
return changed
}
})
this.data = []
this.state = {
listDataSource: ds.cloneWithRows(this.data)
}
here is the function which will update a row
updateRow = (row, rowId, sectionId) => {
// make a shallow clone from the stored data array
let blob = this.data.concat()
// modify the row, since we are using the triple equal operator, we need to make sure we are giving it a new object (new ref)
blob[rowId] = Object.assign({}, blob[rowId], {label: blob[rowId].label + '..cape..deh'})
// tell react to update the source
this.setState({
listDataSource: this.state.listDataSource.cloneWithRows(blob)
}, () => {
// we need to update our data storage here! after the state is changed
this.data = blob
})
}