I'm trying to build a simple app that lets the user type a name of a movie in a search bar, and get a list of all the movies related to that name (from an external public API).
I have a problem with the actual state updating.
If a user will type "Star", the list will show just movies with "Sta". So if the user would like to see the actual list of "Star" movies, he'd need to type "Star " (with an extra char to update the previous state).
In other words, the search query is one char behind the State.
How should it be written in React Native?
state = {
query: "",
data: []
};
searchUpdate = e => {
let query = this.state.query;
this.setState({ query: e }, () => {
if (query.length > 2) {
this.searchQuery(query.toLowerCase());
}
});
};
searchQuery = async query => {
try {
const get = await fetch(`${API.URL}/?s=${query}&${API.KEY}`);
const get2 = await get.json();
const data = get2.Search; // .Search is to get the actual array from the json
this.setState({ data });
} catch (err) {
console.log(err);
}
};
You don't have to rely on state for the query, just get the value from the event in the change handler
searchUpdate = e => {
if(e.target.value.length > 2) {
this.searchQuery(e.target.value)
}
};
You could keep state updated as well if you need to in order to maintain the value of the input correctly, but you don't need it for the search.
However, to answer what you're problem is, you are getting the value of state.query from the previous state. The first line of your searchUpdate function is getting the value of your query from the current state, which doesn't yet contain the updated value that triggered the searchUpdate function.
I don't prefer to send api call every change of letters. You should send API just when user stop typing and this can achieved by debounce function from lodash
debounce-lodash
this is the best practise and best for user and server instead of sending 10 requests in long phases
the next thing You get the value from previous state you should do API call after changing state as
const changeStateQuery = query => {
this.setState({query}, () => {
//call api call after already changing state
})
}
Related
I'm writing a table component for my page in React.
I have a function loadData() that makes a request to an api. Using the api result to update the data state variable, using a new reference.
The problem here is that React doesn't trigger any re-render for the data variable.
const [data, setData] = useState([]);
const loadData = async () => {
try {
...
let response_json = await response.json();
setData(transformData(response_json.items));
...
}
const transformData = (data) => {
if (data === undefined || data === null) {
return [];
}
let new_data = [];
data.forEach((entry,index) => {
new_data.push(cloneElement(props.config.table_entry,{data:entry, key:index}, null));
});
return new_data;
}
I am using this code to change the table's page, making a request with parameters like pageNumber, pageSize, filters. So even with different data and different reference, still it doesn't trigger re-rendering.
This problem has challenged me for like one whole morning was that the data variable continued to updated on every request made but the webpage never re-rendered.
The answer lies here
data.forEach((entry,index) => {
new_data.push(cloneElement(props.config.table_entry,{data:entry, key:index}, null));
});
in the transformData function where it creates a new array of new data, BUT the key property of the component never changed because it was the index of its position in the array returned from the server.
Assigning the key to a unique id solved the problem.
I want to pass the watch time of a video the user has seen when user closes the page,reload the page or navigate to another page. I am using visibilityChange event for this. When i try to navigate to another page, the api call runs perfectly. But the data i am sending to the api is not updated correctly. I am going to provide the code and the output below so you can understand perfectly what my problem is.
useEffect(async () => {
const x = 0;
console.log("use effect is run number ::::", x + 1);
window.addEventListener("visibilitychange", sendViewTime);
return () => {
window.removeEventListener("visibilitychange", sendViewTime);
};
}, []);
I have added the event listener in the useEffect.
the sendViewTime method is the method i want to call on visibility change event. This Method is working perfectly but for some reason the params are not updated even though i have set their states in their relavant hooks.
const sendViewTime = async () => {
if (document.visibilityState === "hidden") {
console.log("the document is hidden");
const value = localStorage.getItem("jwt");
const initialValue = JSON.parse(value);
console.log("the send View Time is :::", played_time);
const params = {
video_id: video_id_url,
viewTime: played_time,
MET: MET_value,
weight: "",
};
console.log("params are :::", params);
await setEffort(params, initialValue).then((res) => {
console.log("set effort api response is ::: ", res);
});
} else {
console.log("the document is back online");
}
};
//This onProgress prop is from react Player. Here i am updating the state of video progress.
onProgress={(time) => {
console.log("the time is :::", time);
const time_1 = Math.round(time.playedSeconds);
const time_2 = JSON.stringify(time_1);
setPlayed_time(time_2);
console.log("the played time is :::", played_time);
}}
//OUTPUT
// the document is hidden.
// the send View Time is :::
//params are ::: {video_id: '23', viewTime: '', MET: undefined, weight: ''}
//set effort api response is ::: {status: 200, message: 'Success', data: {…}, time: '2.743 s'}
//the document is back online
Never mind guys. I found the solution. It seems that i have to pass played_time and met value as a prop to the useEffect.If you want to know how useEffect works please visit this link. In general is it better to use one or many useEffect hooks in a single component?.
Im trying to update two children in my database (using realtime database firebase), but when database is updated, my application go back to the home screen for no reason.
When I update only in "Tasks" it works (the app doesnt go back to the home screen) but when I combine update in "Tasks" and "Users" there is this problem..
Maybe i dont do it the good way.. Any ideas?
statusPlayback = async (status) => {
const { navigation } = this.props
const task = navigation.getParam('task')
//console.log("task = ", task);
//to check if we arrived to the end of the
if (status.didJustFinish) {
const CountVideoRef = firebase
.database()
.ref("Tasks")
.child(firebase.auth().currentUser.uid).child(task.AssignPerson)
.child(task.taskname);
CountVideoRef.once("value").then((snapshot) => {
CountVideoRef.update({
countViewVideo: snapshot.val().countViewVideo + 1,
});
})
const PointEndVideoRef = firebase
.database()
.ref("Users")
.child(firebase.auth().currentUser.uid);
PointEndVideoRef.once("value").then((snapshot1) => {
PointEndVideoRef.update({
Points: snapshot1.val().Points + 10,
});
const points = (snapshot1.val().Points) + 10
//console.log("points = ", points)
this.props.updatePoints({ points: points })
})
this.setState({ showButtonVisible: true });
}
};
I doubt this is the cause of the navigation problem, but this style of updating is fraught with problems:
CountVideoRef.once("value").then((snapshot) => {
CountVideoRef.update({
countViewVideo: snapshot.val().countViewVideo + 1,
});
})
If you need to update an existing value in the database based on its existing value, you should use a transaction to prevent race conditions when multiple users perform the same action around the same time (or while offline).
In this case, you can use the simpler atomic server-side increment operation, which means the above becomes:
CountVideoRef.set(firebase.database.ServerValue.increment(1));
I want fetch data from firebase after that I want to execute another function. Second function have to wait until first one is complete .
this.oAngularFireDatabase.database.ref('Users').orderByKey()
.on('value', snapshot => {
if (snapshot.hasChildren()) {
snapshot.forEach(innerSnap => {
if (innerSnap.hasChild(user.uid)) {
//User role key
this.loggedInUserUserRoleKey = innerSnap.key;
//User id
this.loggedInUserId = user.uid;
//User name
this.loggedInUserName = innerSnap.child(user.uid).child("user_name").val();
if (innerSnap.child(user.uid).hasChild("user_image")) {
//User Image
this.loggedInUserImage = innerSnap.child(user.uid).child("user_image").val();
}
return false;
}
})
}
})
I can't call then function after on it gives me an error.
In my above code, I want call another function after all data are fetch from firebase.
The Firebase on() method can fire multiple times: once when it initially loads the data, and again whenever the data changes. Since a promise (the thing you call then() on) can only resolve once, on() can't return a promise.
There are two options here:
You want to only load the data once.
If this is the case, you should use Firebase's once() method, which does return a promise.
this.oAngularFireDatabase.database.ref('Users').orderByKey()
.once('value').then(snapshot => {
if (snapshot.hasChildren()) {
snapshot.forEach(innerSnap => {
if (innerSnap.hasChild(user.uid)) {
//User role key
this.loggedInUserUserRoleKey = innerSnap.key;
//User id
this.loggedInUserId = user.uid;
//User name
this.loggedInUserName = innerSnap.child(user.uid).child("user_name").val();
if (innerSnap.child(user.uid).hasChild("user_image")) {
//User Image
this.loggedInUserImage = innerSnap.child(user.uid).child("user_image").val();
}
return false;
}
})
}
}).then(value => {
// TODO: perform subsequent action on boolean value
})
You want to listen for changes on the data too.
If this is the case, you should put the subsequent action you want to take into the on() callback:
this.oAngularFireDatabase.database.ref('Users').orderByKey()
.on('value', snapshot => {
if (snapshot.hasChildren()) {
snapshot.forEach(innerSnap => {
if (innerSnap.hasChild(user.uid)) {
//User role key
this.loggedInUserUserRoleKey = innerSnap.key;
//User id
this.loggedInUserId = user.uid;
//User name
this.loggedInUserName = innerSnap.child(user.uid).child("user_name").val();
if (innerSnap.child(user.uid).hasChild("user_image")) {
//User Image
this.loggedInUserImage = innerSnap.child(user.uid).child("user_image").val();
}
}
})
// TODO: perform subsequent action on data
}
})
Note that both of these operations look pretty expensive for what they're trying to accomplish: scanning a JSON tree for a specific value is an anti-pattern in Firebase, and typically means you should modify/augment your JSON to allow a direct lookup or query.
For example, I suspect you now have a structure like /Users/$randomkey/$uid: { ..user data... }. For better performance, consider storing the user data directly under their UID: /Users/$uid: { ..user data... }. This removes the need for a query, and allows you to directly load the data for a user from this.oAngularFireDatabase.database.ref('Users').child(user.uid).
This has me really stumped. I have a method that searches for items in a Firestore database. It works when I call the method directly from a one-off test. It does not work when I call the method from another part of my app with the exact same input.
Here is the method that does the searching:
getProductsStartingWithCategory(textSoFar: string): Observable<Product[]> {
console.log('searching for ', textSoFar);
let endAt = textSoFar + '\uf8ff';
let filteredCollection: AngularFirestoreCollection<Product> =
this.afs.collection('products', ref =>
ref.orderBy('materialType').limit(30).startAt(textSoFar).endAt(endAt)
);
return filteredCollection.snapshotChanges().map(changes => {
return changes.map(a => {
console.log('matched one', a);
const data = a.payload.doc.data() as Product;
data.id = a.payload.doc.id;
return data;
});
});
}
And when I call the method directly from the first page in the app with a test button, it works. That method is as follows:
testTheThing() {
let text: string = 'Car';
this.productService.getProductsStartingWithCategory(text)
.subscribe(data => {
console.log('success', data);
});
}
Again, when I call this method I get results as expected (matching products in the database with materialType 'Carpet'.) Success!
But then, when I use the method from another page in the app, it returns no results. That page is a bit more complicated - essentially the method is being called when user input changes. Here are the relevant parts of the method:
productCategoryChanged(productPurchase: ProductPurchase) {
if (productPurchase.materialType) {
console.log('searching for products starting with "' + productPurchase.materialType + '"');
let unsubscribe = this.productService.getProductsStartingWithCategory(productPurchase.materialType).subscribe(products => {
products.forEach(product => {
// logic goes here...
});
});
// rest of method omitted
In both scenarios, I see the "searching for Car" in the console.log message. The search text is identical. I've tried numerous times with numerous different search text (all of which are in the database). The logging shows the method is being called with the right input, but for some reason I only find results when calling it from the "test" method. Why is that?
I've tried trimming the input. I do have another collection of 'products' hooked up to an observable, but I don't think that matters. I also have used this exact strategy for a "customer" search and that works fine. This "products" search is almost identical but it doesn't work.