How do I loop all Firebase children in React Native? - javascript

ANSWER AT BOTTOM OF POST
(ALSO SEE #soutot ANSWER)
I have successfully gained text output from my Firebase code and so I know it works. Now I need to loop all the children in Firebase. Coming from swift, the way to do it is to store each item on a tempObject then append that to an array. Then you can simply use that array any way you like. This doesn't seem to work with JavaScript, or I'm doing something wrong. The fully functional FB code I now have is:
componentDidMount(){
let child = ""
var ref = firebase.database().ref("testCategory");
ref.once("value")
.then(function(snapshot) {
child = snapshot.child("test1/testHeader").val()
this.setState( {
child
})
}.bind(this));
}
I can then successfully print this in console or in <Text>. Now, the problem I'm having is looping all children, adding them to an array, then using that array to display the data. In my head, this is how it should work:
componentDidMount(){
let child = ""
var ref = firebase.database().ref("testCategory");
ref.once("value")
.then(function(snapshot) {
snapshot.forEach(function(childSnapshot){
let tempObject = MyFirebase
tempObject.testHeader =
childSnapshot.val()
myArray.append(tempObject) //or .push???
})
this.setState( { //PASSING VARIABLE TO STATE
child
})
}.bind(this)); //BINDING TO SET STATE
}
I know that this is obviously wrong. Creating an object like that doesn't even work... MyFirebase -class looks like this:
render() {
let testHeader = ""
let testText = ""
)
}
My Firebase database looks like this:
(I ignored subText for now)
All suggestions are much appreciated :)
WORING CODE
//FIREBASE CODE
componentDidMount(){
const ref = firebase.database().ref("testCategory");
ref.once("value")
.then(function(snapshot) {
const categories = []
//LOOPING EACH CHILD AND PUSHING TO ARRAY
snapshot.forEach(item => {
const temp = item.val();
categories.push(temp);
return false;
});
this.setState( { //PASSING VARIABLE TO STATE
child,
categories
})
}.bind(this)); //BINDING TO SET STATE
}

According to the code you provided, it looks like it works until this point
var ref = firebase.database().ref("testCategory");
ref.once("value")
.then(function(snapshot) {
Am I correct?
From this point, if you add a console.log(snapshot.val()) it might print an array of objects, something like this:
[ { test1: { testHeader: 'FirstHeader', testText: 'FirstText' } }, { test2: { testHeader: 'SecondSub', testText: 'SecondSub } }]
Right?
If so, you can for example store this snapshot into your state and then consume this state in your render method. Something like this:
const ref = firebase.database().ref('testCategory');
ref.once('value').then((snapshot) => {
this.setState({ categories: snapshot.val() });
});
Then in your render method:
const { categories } = this.state;
categories.map(category => <Text>{category.testHeader}</Text>)
The result in your screen should be:
FirstHeader
SecondSub
Let me know if this helped
Some links that might explain more about es6 codes I used in this example:
array map categories.map(...): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
object destructuring const { categories } = this.state: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
const instead of var const ref = ...: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
setState: https://reactjs.org/docs/state-and-lifecycle.html
arrow function (snapshot) => ...: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
Some about firebase snapshots: https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot
Hope it helps

Related

mutate in useSWR hook not updating the DOM

It's a next.js app and I populate the data using a useSWR hook.
const { data, error } = useSWR('/api/digest', fetcher, {
revalidateOnFocus: false,
})
The issue is that the DOM doesn't get updated as expected after the mutate() line below. But if I hard code the data as updatedData and pass it as the arg for the mutate it works normally. The fact is that data and the updatedData are the same. See comments below.
Edit: If I click on any Navbar <Link/> it gets updated.
Any clues of what is happening?
const handleAddRowClick = async () => {
const newRow = { category: selectedCategory, entity: selectedEntity }
data.subscription[selectedCategory].push(selectedEntity);
console.log(data); // This data is equal to updatedData
const updatedData = {
"subscription": {
"materialFact": [
"Boeing"
],
"headlines": [
"thejournal",
"onemagazine" // ***This is the value added.
]
}
}
// mutate('/api/digest', data, false) // This does not works.
mutate('/api/digest', updatedData , false) // It works.
}
I am assuming that the data property you use in handleAddRowClick is the same that you get from useSWR. Now, if you update some deeply nested object in data, it doesn't change the data reference. It still points to the old object. So, if you pass it again to mutate, for mutate, the object is still the same and hence, it won't trigger a re-render.
I would recommend that you do something like the following to derive updatedData from data and then pass it to the mutate function.
const handleAddRowClick = async () => {
const updatedData = {
...data,
subscription: {
...data.subscription,
[selectedCategory]: [
...data.subscription[selectedCategory],
selectedEntity,
]
}
}
mutate('/api/digest', updatedData , false);
}
On a side note, you can also use something like immer to simplify copying the state.

how to update items object after on onTextChange in searchable dropdown in react native?

I am using react-native-searchable-dropdown for my react native project.
When onTextChange is fired I make a call to my backend with the text the user inputs.
Then I set the data I get back (array of objects) in the state:
async inputBrandText (text) {
const brands = await SearchForBrands(params);
this.setState((state) => {
return state.brands = brands
})
};
In the items element I reference this state array:
<SearchableDropdown
textInputProps={
{
placeholder: "",
underlineColorAndroid: "transparent",
style: {
...
},
onTextChange: text => this.inputBrandText(text)
}
}
items={this.state.brands}
/>
But the items get refreshed one key stroke after it should actually be refreshed.
So I think when the onTextChange event is fired, the items object or array is read in.
Then I change the this.state.brands object with the data from the backend.
But then no update occurs and the items object is not updated. Therefor the old items object is shown.
How can I solve this problem in a class based component?
Thanks!
PS: This is my ComponendDidMount:
async componentDidMount () {
const brands = await SearchForBrands(params);
this.setState((state) => {
return state.showBrandSuggestions = brands
})
}
Try this way
Your componentDidMount method should be like
async componentDidMount () {
const brands = await SearchForBrands(params);
this.setState({ showBrandSuggestions: brands });
}
Your inputBrandText method should be like
async inputBrandText (text) {
const brands = await SearchForBrands(params);
this.setState({ brands: brands });
};

useMemo function running during rendering

Basically what I'm trying to do is call this 'myMemoFunction' function in a part of my code. The problem is that from what I've read in the documentation, useMemo() executes this function right on rendering, therefore the myArray parameter is still empty. Then it returns the error below.
const myMemoFunction = useMemo((myArray) => {
const users = myArray.map((a) => a.user)
return users;
})
Error:
myArray is undefined
You should use useCallback in that case
as useMemo memoizes a variable. Also I doubt it can take arguments.
Edit:
const myMemoFunction = useCallback((myArray) => {
// this won't be called on renders
const users = myArray.map((a) => a.user)
return users;
}, [] /* dont forget the dependency you want to evaluate only once */)
// later
myMemoFunction(arr);
Edit 2 with useMemo:
const myMemoVariable = useMemo(() => {
// re-evaluates each time myArray changes
const users = myArray.map((a) => a.user)
return users;
}, [myArray])
// note that we dont use myMemoVariable() to get our variable
console.log(myMemoVariable)
update your code to this
const myMemoFunction = useMemo((myArray) => {
// myArray could be undefined, that was why ? was added
const users = myArray?.map((a) => a.user)
return users;
}, [myArray]) // myArray is in a dependency of the function
your memo function will get updated with the new myArray variable
it is always good to initialize your myArray variable as an array e.g []

Accessing index of an array returns undefined

i am building a simple React app where i collect data from a Firebase realtime database and push it as an array (memberArr) into the component state via setState:
this.setState({members: memberArr})
When I log the state in the render() method, I see the contents of the array. In the React Dev Tools the state is also filled as expected.
If I now want to access the contents of the array (e.g. with this.state.members[0]) the console returns undefined.
I initialize the state like this:
constructor(props) {
super(props);
this.state = {
members: [],
};
}
My whole componentDidMount() method looks like this:
componentDidMount() {
const membersRef = firebase.database().ref(`${groupName}/members`);
membersRef.on('value', (data) => {
const memberArr = [];
data.forEach(function(snapshot){
//Gets name of member
var memberName = snapshot.val().name;
//Gets UID of member
var memberKey = snapshot.key;
//Get expenses from fetched member. When ready call function to push everything as array into component state.
this.getExpensesFromUser(memberKey).then(function(data) {
pushArray(data)
});
function pushArray(memberExpense) {
memberArr.push([memberName, memberKey, memberExpense])
};
//This works:
//memberArr.push([memberName, memberKey])
}.bind(this));
this.setState({members: memberArr})
});
}
On a side note: If I avoid calling
this.getExpensesFromUser(memberKey).then(function(data) {
pushArray(data)
});
and only use the following to push the name and key of the members:
memberArr.push([memberName, memberKey])
everything works as expected. console.log(this.state.members[0][0]) returns the name of the first member after the render() method gets triggered due to setState.
Do you have any tips for me?
As Dragos commented, your getExpensesFromUser function returns asynchronous results, so you need to make sure to only call setState once those calls have finished.
const membersRef = firebase.database().ref(`${groupName}/members`);
membersRef.on('value', (data) => {
const promises = [];
data.forEach((snapshot) => {
var memberKey = snapshot.key;
promises.push(this.getExpensesFromUser(memberKey))
});
Promise.all(promises).then((data) => {
const memberArr = data.map((item) => [memberName, memberKey, memberExpense]);
this.setState({members: memberArr})
});
});

How to create multiple copies of a deeply nested immutable Object?

I'm trying to get data from firebase/firestore using javascript so i made a function where i get my products collection and passing this data to reactjs state.products by setState() method
My goal is to pass these products to my react state but also keeping the original data and not changing it by manipulating it. I understand that in JavaScript whenever we are assigning the objects to a variable we are actually passing them as a reference and not copying them, so that' why i used the 3 dots (spread syntax) to copy firestore data into tempProducts[] same way to copy it in virgins[]
getData = () => {
let tempProducts = [];
let virgins = [];
db.collection("products")
.get()
.then(querySnapshot => {
querySnapshot.forEach(item => {
const singleItem = { ...item.data() };
virgins = [...virgins, singleItem];
tempProducts = [...tempProducts, singleItem];
});
tempProducts[0].inCart = true;
this.setState(
() => {
return { products: tempProducts };
},
() => {
console.log(
"this.state.products[0].inCart: " + this.state.products[0].inCart
);
console.log("tempProducts[0].inCart: " + tempProducts[0].inCart);
console.log("virgins[0].inCart: " + virgins[0].inCart);
}
);
});
};
then calling this method in componentDidMount
componentDidMount() {
this.getData();
}
So I changed the first product inCart value in tempProducts to true when I console log tempProducts value and state.products value it gives me true all fine but I'm expecting to get false when i console log virgins value but i did not. I should also mention that all inCart values are false in firestore data.
I solved the issue by passing the original firestore data to virgins[] instead of the singleItem as it is an object referenced by tempProducts[] like so virgins = [...virgins, item.data()]; also it works if i copied the singleItem object instead of referencing it like so virgins = [...virgins, { ...singleItem }]; keep in mind that i have no idea if this solutions are in fact efficient (not "memory waste") or not.

Categories

Resources