I am very new to react and Firebase and I really struggle with arrays and objects I'm guessing that you can't use .map with the way my data is formatted (or type). I've looked through stack but nothing has helped (at least in my poor efforts to implement fixes).
I am trying to use .map to map through a result from firebase but I get the error TypeError: this.state.firebasedata.map is not a function.
getting the data:
componentWillMount(){
this.getVideosFromFirebase()
}
getVideosFromFirebase(){
var youtubeVideos = firebase.database().ref('videos/');
youtubeVideos.on('value', (snapshot) => {
const firebasedata = snapshot.val();
this.setState({firebasedata});
});
}
relevant states:
constructor(props){
super(props);
this.state = {
firebasedata: []
}
};
.map in render:
render(){
return(
<div>
{this.state.firebasedata.map((item) =>
<div key="{item}">
<p>{item.video.name}</p>
</div>
)}
</div>
);
}
This is because firebase does not store data as arrays, but instead as objects. So the response you're getting is an object.
Firebase has no native support for arrays. If you store an array, it really gets stored as an "object" with integers as the key names.
Read this for more on why firebase stores data as objects.
To map over objects you can do something like
Object.keys(myObject).map(function(key, index) {
console.log(myObject[key])
});
For anyone coming here now, you can simply type snapshot.docs to get an array of all the documents in the relevant collection.
As an example, if you want to get all user objects from the collection users you can do the following:
const getAllUsers = async () => {
const usersSnapshot = await db.collection('users').get()
const allUsers = usersSnapshot.docs.map(userDoc => userDoc.data())
return allUsers
}
As noted above Firebase snap.val() - is an object.
When you have to go through object you can also simply use for each:
for(var key in this.state.firebasedata){
<div key="{key}">
<p>{this.state.firebasedata[key].video.name}</p>
</div>
}
I also recommend creating a separate method for it, and call it in render.
Hope this helps
Related
I’m on the web dev learning journey. Most recently I’ve been learning React Router and I’m excited to be close to building my first full stack (MERN) apps! The tutorial is building a basic contacts app.
I'm perplexed by a specific function in this tutorial that is supposed to update contact data. Here is the section of the tutorial where the function is called. Here is the module with the pertinent problem functions (additional functions were trimmed out for brevity):
import localforage from "localforage";
import { matchSorter } from "match-sorter";
import sortBy from "sort-by";
export async function updateContact(id, updates) {
await fakeNetwork();
let contacts = await localforage.getItem("contacts");
let contact = contacts.find(contact => contact.id === id);
if (!contact) throw new Error("No contact found for", id);
Object.assign(contact, updates);
await set(contacts);
return contact;
}
function set(contacts) {
return localforage.setItem("contacts", contacts);
}
The updateContact function doesn't add up to me because I see no place where the contacts variable, which is an array of objects, is being updated with the updated contact object, and I don't see an updated version of the contacts object being persisted to local storage, only what appears to be the same non-mutated/non-updated contacts object. The confusion is because the app actually does work, it does update the contact, but I do not understand how that is happening given this code.
If more context is needed please let me know...I don't think I can add too many additional code snippets because it thinks it's spam?
I've looked through the React Router documentation and I do not see how anything in the documentation could explain what I'm seeing. Using console.log in the function only made me more confused.
Any help is appreciated in pointing out what I might be missing!
This code is mutating an object reference. Object.assign(contact, updates); "merges" in the updates into the contact object which is a reference to an object in the contacts array.
The Object.assign() static method copies all enumerable own
properties from one or more source objects to a target object. It
returns the modified target object.
The syntax is Object.assign(target, ...sources). See Object.assign for more details.
Once a specific contact object is found, it's mutated, and the updated data is persisted back into localStorage.
export async function updateContact(id, updates) {
await fakeNetwork();
let contacts = await localforage.getItem("contacts");
// contact is reference to object in contacts
let contact = contacts.find(contact => contact.id === id);
if (!contact) throw new Error("No contact found for", id);
// mutate the contact object
Object.assign(contact, updates);
// persist updated data back to localStorage
await set(contacts);
return contact;
}
A more familiar "React-y" way might look something like:
export async function updateContact(id, updates) {
await fakeNetwork();
const contacts = await localforage.getItem("contacts");
const contact = contacts.find(contact => contact.id === id);
if (!contact) {
throw new Error("No contact found for", id);
}
const newContacts = contacts.map(contact => contact.id === id
? { ...contact, ...updates } // <-- create new object reference and merge
: contact
);
await set(newContacts);
return contact;
}
I need help to understand how to fetch external data using server-side rendering with the getServerSideProps method. My real problem is with the search itself, more specifically with the type of data that is returned and how to deal with it. I am not an experienced javascript programmer, so I think this is more of a javascript issue than next.js
This is where I fetch inside getServerSideProps:
export async function getServerSideProps() {
const url = `https://...`;
const options = {
< header info...> };
const res = await fetch(url, options);
const data = await res.json();
const orders = data.list; **// here is where the problem might be**
return { props: { orders } };
}
Here where I render from getServerSideProps:
const ListOrders = ({ orders }) => {
return (
<div>
{orders.map((o, i) => (
<h3 key={i}>{o.orderId}</h3>
))}
</div>
);
};
Here the object that I fetch. The data that interests me is the list.
{
"list":[...]
"data1":[]
"data2":{...}
"data3":{...}
}
I would appreciate any help on that. Thanks in advance!
this error shows data orders is undefined. First thing when you map, in case you dont have any orders array, you have to add a guard to your code, so your app wont break.
<div>
{orders && orders.map((o, i) => (
<h3 key={i}>{o.orderId}</h3>
))}
</div>
this is called short circuiting, orders.map will run if orders is defined.
Since you make an api request. make sure that api server allows your app to do fetcing. Normally you cannot fetch data from external server, unless it allows you.
console.log() does not run inside getServerSideProps(). It is important that you always examine the data that you are fetching. Instead of passing orders pass whole data const data = await res.json() as prop. return { props: { data } }
Now inside the component, console.log(data) see if you ever fetched any data, if yes examine the structure.
You should check structure of orders data before you start using it
U can only map on arrays
The error say that there no variable named undefined
For eg if we call the function and assign result to a variable
let orders = getServerSideProps()
//The data structure of orders variable be like this
/*
orders = {
props : {
orders :{ data1 : [ ], data2 : [ ] }
}
*/ }
// If you map on data1 you need to access like this
orders.props.orders.data1.map(eachdata=>{
// stuff that you want to do here
})
my store:
store={
users: [
{id:1},
{id:2},
{id:3}
]
}
Now, I want to import only one of the users based on the route params eg.
id = this.props.match.params.user_id;
The question is.. How to establish the id variable above before the mapStateToProps function fetches the data from my store? My mapStateToProps:
return {
singleUser: state.users[id]
};
Returns undefined. I tried placing the variable 'id' inside the mapStateToProps but I THINK the params aren't established by the time it starts fetching data.
EDIT: Also, I can't establish the id variable in mapStateToProps because this.props is undefined outside of the component.. how would i go about that?
You can use the array method find to get the object with the matching id.
return {
singleUser: state.users.find(user => user.id === id)
};
You can filter the user object to get the desired user.
singleUser: state.users.filter(user => {user.id == id})[0];
filter will return an array so you will need [0] to get the matched object. In your actual code, you might want to handle edge cases like when the ID doesn't exist.
Also, if you are not targetting internet explorer you can try find. It returns the value of the first element in the array that satisfies the provided testing function.
return {
singleUser: state.users.find((user) => {user.id === id})
};
const mapStateToProps = (state, ownProps) => {
const id = ownProps.match.params.product_id;
return {
user: state.users.find(user => user.id === Number(id))
};
};
The this.props of the component can be made available through the second parameter of mapStateToProps(). ownProps in the example above.
I have an array that I'm converting to Ember array with A() as I want to use some of Ember array methods, like filterBy(), but it's not producing the result I want. What is the proper way to convert a vanilla array into an Ember array?
Ember:
import Component from '#ember/component';
import { computed } from '#ember/object';
import { A } from '#ember/array';
export default Component.extend({
movieGenreIds: computed.map('movies', function(movie, index) {
return movie.genre_ids;
}),
genresNames: computed('movieGenreIds', 'allGenres', function() {
let genresArray = A(this.get('genres')); // <--- conversion here
this.get('movieGenreIds').forEach((movieGenreId, movieGenreIndex) => {
console.log('MOVIE_GENRE_IDS!!!', genresArray);
console.log('FILTERBY ID^^^', genresArray.filterBy('id', movieGenreIds.toString())); // <-- not returning desired results
});
}),
});
Ember route (data is from themoviedb api and models represent the data structure in the json provided):
import Route from '#ember/routing/route';
import RSVP from 'rsvp'
export default Route.extend({
model() {
return RSVP.hash({
movies: this.store.findAll('movie'),
genres: this.store.findAll('genre'),
})
.then(data => data);
},
});
Okay, first .then(data => data); makes literally nothing. Just remove this.
Next if you don't disable the prototype extension for Arrays you dont need to convert normal arrays to ember arrays. so replace this:
let genresArray = A(this.get('genres'));
with this:
let genresArray = this.get('genres');
or this in ember 3.1+:
let genresArray = this.genres;
Now an interesting question is, what is genre_ids on the movie model? I strongly assume its a computed property that returns an array. But some code would help.
However your dependency key for movieGenreIds is wrong. You probably should do this:
movieGenreIds: computed.map('movies.#each.genre_ids', function(movie, index) {
return movie.genre_ids;
}),
However your actual problem is now probably that this will probably return an array of arrays. So something like this:
[[1,2],[3],[],[4,5]]
Now you do .forEach on it, however movieGenreId will now probably still be an array. Next you do movieGenreId.toString() (you actually do movieGenreIds.toString(), but I assume this is a typo, because this wouldn't make sense because you don't use movieGenreId inside the loop then). However doing .toString() on an array will probably not give you the desired result - an id.
And so probably your fix is to fix the movieGenreIds CP (the code is for ember 3.1+):
movieGenreIds: computed('movies.#each.genre_ids', function() {
return this.movies
.map(m => m.genre_ids)
.reduce((a, b) => [...a, ...b]);
}),
From the Firebase note:
Given a single key path like alanisawesome, updateChildren() only updates data at the first child level, and any data passed in beyond the first child level is a treated as a setValue() operation. Multi-path behavior allows longer paths (like alanisawesome/nickname) to be used without overwriting data. This is why the first example differs from the second example.
I am trying to use a single function createOrUpdateData(object) in my code. In case of update, it updates first level children properly, but if I have nested object passed, then it deletes all other properties of that nested object.
Here's the code:
function saveUserDetails(email,object){
var hashedEmail = Utilities.getHashCode(email);
var userRef = ref.child(hashedEmail);
return $q(function(resolve,reject){
return userRef.update(object, function(error){
if(error){
reject(error);
}else{
resolve("Updated successfully!");
}
});
});
}
So if I pass:
{
name: 'Rohan Dalvi',
externalLinks: {
website: 'mywebsite'
}
}
Then it will delete other properties inside externalLinks object. Is there a cleaner and simpler way of avoiding this?
In short, how do I make sure nested objects are only updated and that data is not deleted.
You can use multi-path updates.
var userRef = ref.child(hashedEmail);
var updateObject = {
name: 'Rohan Dalvi',
"externalLinks/website": 'mywebsite'
};
userRef.update(updateObject);
By using the "externalLinks/website" syntax in the object literal it will treat the nested path as an update and not a set for the nested object. This keeps nested data from being deleted.
This question provides a more recent solution that works with cloud firestore.
Rather than using "/", one may use "." instead:
var userRef = ref.child(hashedEmail);
var updateObject = {
name: 'Rohan Dalvi',
"externalLinks.website": 'mywebsite'
};
userRef.update(updateObject);
To update nested object/map/dictionary in firebase database, you can use Firestore.Encoder to class/struct that is Codable.
Here is a Swift code example:
Models:
import FirebaseFirestore
import FirebaseFirestoreSwift
// UserDetails Model
class UserDetailsModel: Codable {
let name: String,
externalLinks: ExternalLinkModel
}
// UserDetails Model
class ExternalLinkModel: Codable {
let website: String
}
Calling Firebase:
import FirebaseFirestore
import FirebaseFirestoreSwift
let firestoreEncoder = Firestore.Encoder()
let fields: [String: Any] = [
// using firestore encoder to convert object to firebase map
"externalLinks": try! firestoreEncoder.encode(externalLinkModel)
]
db.collection(path)
.document(userId)
.updateData(fields, completion: { error in
...
})