I stuck the change methods.properly fetch JSON to normal method - javascript

enter image description here
export default class App extends Component {
state = {
currentCategory: "",
products: [],
cart: []
};
componentDidMount() {
this.getProducts();
}
changeCategory = category => {
this.setState({
currentCategory: category.categoryName
});
this.getProducts(category.id);
};
getProducts = categoryId => {
let url = "http://localhost:3000/products";
if (categoryId) {
url += "?categoryId=" + categoryId;
}
fetch(url)
.then(response => response.json())
.then(data => this.setState({
products: data
}));
};
//////////////////////////////////////////////////
export default class App extends Component {
state = {
currentCategory: "",
products: [],
cart: []
};
componentDidMount() {
this.getProducts();
}
changeCategory = category => {
this.setState({
currentCategory: category.categoryName
});
this.getProducts(category.id);
};
getProducts = (categoryId) => {
var item = db.products;
console.log(item.categoryId);
this.setState({
products: item
});
}
}
/////Json format like shown below//////
{
"products": [{
"id": 1,
"categoryId": 1,
"productName": "Chai",
"quantityPerUnit": "48 - 6 oz jars",
"unitPrice": 23,
"unitsInStock": 53
}],
"categories": [{
"id": "1",
"categoryName": "Beverages",
"seoUrl": "beverages"
}]
}
in this first part work correctly when i fetch data from api. But when i wanna fetch json data from local its fine i can but not work properly as first method. At the first method filter products by categoryId but i cant handle this filter.
console.log(item.categoryId); with this code i try to look can i get proper id's item on console but İ can see only undefined and if i use this.setState({products:item.categoryId}); then i take an error of about map function.
in this picture when i click one of category at the left side its filter the right side of products. İ can use this only first code i couldnt change properly.

Related

why this.setState going to "is not a function"?

Im trying build simple app to learn how to make api calls.
When I'm trying to setState in react with onClick function I'v created, every time I'v tried to invoke I get
not a function
I tried to bind this like this:
this.setState(() => ({
rowPos: null
}).bind(this))
but that did not worked also, I get the error:
(intermediate value).bind is not a function
there is constructor with state object:
constructor(props) {
super(props);
this.state = {
endpoint: 'https://jolapatola5.fakturownia.pl/invoices.json',
params: {
api_token: 'B5Lg3uPBCMcDNX5lsQOM/jolapatola5',
invoice: {
"kind": "vat",
"number": null,
"sell_date": "2019-07-14",
"place": 'Lublin',
"sell_date": "2019-07-14",
"issue_date": "2019-07-14",
"payment_to": "2019-07-21",
"buyer_name": "aaa",
"buyer_tax_no": "5252445767",
"buyer_street": "aaa",
"buyer_post_code": "",
"buyer_city": "",
"seller_name": 'aaa',
"seller_street": '',
"seller_post_code": '',
"seller_city": '',
"seller_bank_account": '',
"seller_tax_no": '',
positions: [{
"name": "Produkt A1",
"tax": 23,
"total_price_gross": 10.23,
"quantity": 1
},
{
"name": "Produkt A1",
"tax": 23,
"total_price_gross": 10.23,
"quantity": 1
}
]
}
}
}
this.removeProductRow = this.removeProductRow.bind(this);
}
and method I'm trying to invoke onClick:
removeProductRow(id) {
let rowPos = this.state.params.invoice.positions[id];
this.setState(() => ({
rowPos: null
}).bind(this))
console.log(rowPos)
};
id is passed when I'm mapping components
The result I'm trying to perform is set the this.state.params.invoice.position alias rowPos to null, now its an object.
Thanks for any help
EDIT: there is the way I'm mapping components:
{
this.state.params.invoice.positions.map(function(item,index){
return
<ItemRow key={index} removeProductRow={() => this.removeProductRow(index)}/>
},this)
}
setState should be binded to React.Component, when you call this.setState.bind(this) you are actually binding it to removeProductRow, just remove the .bind(this)
There are two things I would do different.
First: Fixing the remove method.
removeProductRow(index){
let positionsUpdated = this.state.params.invoice.positions.filter((_, idx) => idx !== index);
// update just the positions of the state.
// try to create paramsUdpated yourself.
this.setState({params: paramsUdpated});
};
Second: In render I would not pass callback to props, just the name of the function and use prop itemIndex to get the index of the positions array in ItemRow component.
{this.state.params.invoice.positions.map(function(item,index){
return (<ItemRow key={index} itemIndex={index} removeProductRow={this.removeProductRow}/>)}
Working example of my idea: https://codesandbox.io/s/priceless-sun-tb76r

How can I join data with React native and a many to many firebase firestore collection relationship?

I am new to react native and firebase but understand SQL relationships. I am trying to understand how to model many to many relationships with firebase in NoSQL.
In my scenario, users can own multiple groups and groups can have multiple user owners.
I want to display group information (name, city state) for a particular user. (In this case, doc id: WQQZ6pcMMgQoTxH9cr2XGaPOq9W2).
This is how data is structured in the firebase filestore:
{
"__collections__": {
"Group": {
"group3": {
"city": "Aurora",
"state": "CO",
"name": "Group Three",
"__collections__": {}
},
"group4": {
"state": "CO",
"name": "Group Four",
"city": "Denver",
"__collections__": {}
},
"group5": {
"city": "Aurora",
"state": "CO",
"name": "Group Five",
"__collections__": {}
}
},
"User": {
"Hm56Zhn9TJP9jVYrzJRqHAB8T8H3": {
"last_name": "Sneed",
"first_name": "Sam",
"__collections__": {}
},
"WQQZ6pcMMgQoTxH9cr2XGaPOq9W2": {
"last_name": "Smith",
"first_name": "Will",
"__collections__": {}
}
},
"UserGroups": {
"F4GubhZKqWcJnHahL0TQTOP62Jj1": {
"group3": true,
"group4": true,
"__collections__": {}
},
"WQQZ6pcMMgQoTxH9cr2XGaPOq9W2": {
"group5": true
"__collections__": {}
}
}
}
}
Here is my code:
export default class GroupSelect extends Component {
constructor(props) {
super(props);
this.userGroupRef = firebase.firestore().collection("UserGroups").doc('WQQZ6pcMMgQoTxH9cr2XGaPOq9W2');
this.state = {
userGroups: [
{
uid: 'group1',
name: 'Group One',
city: 'Chicago',
state: 'IL'
},
{
uid: 'group2',
name: 'Group Two',
city: 'Denver',
state: 'CO'
}
]
}
}
componentDidMount() {
this.userGroupRef.get().then((doc) => {
var obj = doc._data;
var group_ids = Object.keys(obj).map(function (key) {
return key;
});
var groups = [];
var group = {};
group_ids.forEach(function (group_id) {
console.log(group_id);
this.groupRef = firebase.firestore().collection('Group').doc(group_id);
this.groupRef.get().then((groupDoc) => {
group.name = groupDoc._data['name'];
group.city = groupDoc._data['city'];
group.state = groupDoc._data['state'];
groups.push(group);
});
});
console.log(groups); //not populated correctly
// this.setState({
// userGroups: groups
// });
});
}
render() {
return (
this.state.userGroups.map((userGroup, index) => (
<Text>{userGroup.name} - {userGroup.city}, {userGroup.state}</Text>
))
)
}
}
If I comment out everything in ComponentDidMount(), the render() shows the original contents of state.UserGroups correctly. But, when I try to populate the array in the ComponentDidMount() and reset the state userGroups var, there is a problem with the join and the timing of the population of the array.
How best to do this?
I am patterning this off of how firebase joins are described here:
https://www.youtube.com/watch?v=Idu9EJPSxiY
Users, eventAttendees and Events many-to-many join in firebase realtime db
But, this uses the realtime database instead of filestore, which is what I want to use.
I want to do the same but with: Users, UserGroups, and Groups
Should the data be structured differently?
I implemented Frank van Puffelen's answer below.
It works but that brings up other questions.
If I use the UserGroups collection as described...
constructor(props) {
super(props);
this.userGroupRef = firebase.firestore().collection("UserGroups").doc(firebase.auth().currentUser.uid);
this.state = {
userGroups: []
}
}
...it works, but see possible "issues" below.
componentDidMount() {
this.userGroupRef.get().then((doc) => {
var obj = doc._data;
//get array of group ids
var group_ids = Object.keys(obj).map(function (key) {
return key;
});
let promises = [];
//issue: making singleton database calls for each group id, maybe not good.
group_ids.forEach(function (group_id) {
promises.push(firebase.firestore().collection('Group').doc(group_id).get())
});
let groups = [];
let parentThis = this; //issue: get ref to parent this to set in Promise.all below, kinda weird, maybe not so clear
Promise.all(promises).then(function(docs) {
docs.forEach((groupDoc) => {
let group = {};
group.name = groupDoc._data['name'];
group.city = groupDoc._data['city'];
group.state = groupDoc._data['state'];
groups.push(group);
});
parentThis.setState({
userGroups: groups
});
});
});
}
If I restructure data and add a groups collection below User...
"User": {
"WQQZ6pcMMgQoTxH9cr2XGaPOq9W2": {
"last_name": "Carter",
"first_name": "Will",
"__collections__": {
"Groups": {
"group1": {
"city": "Chicago",
"state": "IL",
"name": "Group One",
"__collections__": {}
},
"group2": {
"city": "Denver",
"state": "CO",
"name": "Group Two",
"__collections__": {}
}
}
}
}
}
Then, code becomes more straightforward.
constructor(props) {
super(props);
//path: User/WQQZ6pcMMgQoTxH9cr2XGaPOq9W2/Groups
this.userGroupRef = firebase.firestore().collection("User").doc(firebase.auth().currentUser.uid).collection("Groups");
this.state = {
userGroups: []
}
}
componentDidMount() {
this.userGroupRef.get().then((doc) => {
let groups = [];
doc._docs.forEach(function (groupDoc) {
let group = {};
group.name = groupDoc._data['name'];
group.city = groupDoc._data['city'];
group.state = groupDoc._data['state'];
groups.push(group);
});
this.setState({
userGroups: groups
});
});
}
This method seems cleaner to me. But now I have duplicate data in a root Group collection and under User collection.
Is it better to do it in this method with duplicate Group data in the db?
I have a relational db background and learning NoSQL best practices. My instinct is not to duplicate data in the db.
You're printing console.log(groups) outside of the callback where the groups get loaded. That won't work, as data is loaded from Firestore asynchronously, and your log statement runs before any data is loaded.
This is easiest to see if you place a few simple log statements:
console.log("Start loading documents");
group_ids.forEach(function (group_id) {
this.groupRef = firebase.firestore().collection('Group').doc(group_id);
this.groupRef.get().then((groupDoc) => {
console.log("Loaded document");
});
});
console.log("Started loading documents");
When you run this code, it outputs:
Start loading documents
Started loading documents
Loaded document
Loaded document
...
This is probably not the order that you expected, but it perfectly explains why the groups array is empty when you print it: none of the groups has been loaded yet. In fact, if you log groups insode the then() callback, you'll see it getting populated one document at a time.
Any code that needs the documents, needs to either be inside the callback, or wait for the document(s) to be loaded by using a promise. Since you're waiting for multiple documents, use Promise.all():
var promises = [];
group_ids.forEach(function (group_id) {
promises.push(firebase.firestore().collection('Group').doc(group_id).get())
});
Promise.all(promises).then(function(docs) {
docs.forEach((groupDoc) => {
group.name = groupDoc._data['name'];
group.city = groupDoc._data['city'];
group.state = groupDoc._data['state'];
groups.push(group);
});
console.log(groups);
});
This video may answer my question.
... at 13:31: "this would probably be duplicate data that would live both in the top-level user object and in this individual review and we'll talk in future videos about the best strategies to keep these kinds of things consistent."
This leads me to believe that I should duplicate the needed group data under the user and not do the joining bit in the client. https://www.youtube.com/watch?v=v_hR4K4auoQ
Or, maybe not:
From this video: https://www.youtube.com/watch?v=jm66TSlVtcc
... at 7:17 "If you have a sql background, this is very similar to using an intermediate table for joins"
More details here: https://angularfirebase.com/lessons/firestore-nosql-data-modeling-by-example/#Subcollection-Many-to-Many-Relationships
In the structure above, how would you get all the tweets that a user has liked(hearted) using the hearts table that has the foreign keys with a single query?

Is there a process/practice for replacing a JSON value? Specifically, replacing an API URL with a value provided by the API

I'm using the Star Wars API to practice, but I am running into a weird bug. I'm pretty sure I am the problem here but I don't know enough about these processes to find the issue.
I am creating profile cards with this info, but when I try to replace the homeworld url with the actual name, I am unable to change the value that appears in my react element.
This is a smaller version of the JSON object that I get from the API.
{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "https://swapi.co/api/planets/1/",
},
I was trying to replace the url value of homeworld with the name of the actual homeworld before saving it to my this.state array. I've tried making the fetch calls from the element files (that didn't really feel proper). So I hacked some code together and watched it change with console.log();. It's not the prettiest.
fetch('https://swapi.co/api/people/')
.then(response => {
return response.json();
})
.then(array => {
console.log(array.results);
Promise.all(array.results.map(character => {
console.log(character.homeworld)
let home_url = character.homeworld;
fetch(home_url)
.then(home => {return home.json()})
.then(home_json => character.homeworld = home_json.name)
}))
.then(() => {
console.log(array.results)
this.setState({characters:array.results})
});
});
The console.log(); shows that the value for homeworld was changed to the string 'Tatooine'. This is the same all the way down to the profile card. So I was expecting this to be the value in the card to be 'Tatooine', but I end up with "https://swapi.co/api/planets/1/".
At this point I don't know where my lack of knowledge is. I'm not sure if it is an issue with JSON, React, Fetch/Promises. So if anyone is able to offer some insight on this issue that would be great. I can add more code to the post if needed. Cheers!
You need to return something in each .then call in order to keep passing updated data along. Also in Promise.all( array.results.map( you should return each element so that you don't end up with an array full of undefined.
Here is an example of how you can do this (note I'd recommend using async/await for at least the Promise.all section):
componentDidMount() {
fetch("https://swapi.co/api/people/")
.then(response => response.json())
.then(array => {
console.log(array.results);
return Promise.all(array.results.map(async character => {
console.log(character.homeworld);
const homeUrl = character.homeworld;
const home = await fetch(homeUrl);
const homeJson = await home.json();
return {
...character,
homeworld: homeJson,
}
}));
})
.then(characters => {
console.log(characters);
this.setState({ characters });
})
}
Again using async/await everywhere:
componentDidMount() {
this.fetchData();
}
async fetchData() {
const response = await fetch("https://swapi.co/api/people/");
const array = await response.json();
console.log(array.results);
const characters = await Promise.all(array.results.map(async character => {
console.log(character.homeworld);
const homeUrl = character.homeworld;
const home = await fetch(homeUrl);
const homeJson = await home.json();
return {
...character,
homeworld: homeJson,
}
}));
console.log(characters);
this.setState({ characters });
}
Then this.state.characters is an array of length 10. Here is a sample element:
{
birth_year: "41.9BBY"
created: "2014-12-10T15:18:20.704000Z"
edited: "2014-12-20T21:17:50.313000Z"
eye_color: "yellow"
films: (4) ["https://swapi.co/api/films/2/", "https://swapi.co/api/films/6/", "https://swapi.co/api/films/3/", "https://swapi.co/api/films/1/"]
gender: "male"
hair_color: "none"
height: "202"
homeworld: {name: "Tatooine", rotation_period: "23", orbital_period: "304", diameter: "10465", climate: "arid", …}
mass: "136"
name: "Darth Vader"
skin_color: "white"
species: ["https://swapi.co/api/species/1/"]
starships: ["https://swapi.co/api/starships/13/"]
url: "https://swapi.co/api/people/4/"
vehicles: []
}

How to access nested array inside a JSON object from javascript

I am working with a json object that has nested arrays as well as names with spaces such as Account ID. I need to display just the Account ID's in my Vue.js application. I am able to get my entire response.data json object but not too sure how to get just the Account ID when it's nested like the example below.
JSON
"response": {
"result": {
"Accounts": {
"row": [
{
"no": "1",
"FL": [
{
"val": "ACCOUNT ID",
"content": "123456789"
},
...
Vue.js
<script>
import axios from "axios";
export default {
name: 'HelloWorld',
data () {
return {
accounts: [],
accountIDs: []
}
},
mounted() {
var self = this;
axios.get('https://MYAPIGETREQUEST')
.then( function(res){
self.accounts = res.data;
self.accountIDs = //This is where I want to get the Account ID
console.log('Data: ', res.data);
})
.catch( function(error){
console.log('Error: ', error);
})
}
}
</script>
Try something like this
if(res.data.response.result.Accounts.row[0].FL[0].val === 'ACCOUNT ID') {
self.accountIDs = res.data.response.result.Accounts.row[0].FL[0].content;
...
}
You can also try something like this:
let rowAccounts = response.result.Accounts.row
.map(row => row.FL
.filter(FL => FL.val === 'ACCOUNT ID')
.map(acc => acc.content)
);
self.accountIDs = [].concat.apply([], rowAccounts);
In rowAccounts, you get and array of accounts array per row like:
[
0: ['acc row 1', 'another acc row1'],
1: ['acc row 2'....]
]
Now it all depends upon your implementation the way you like it.

Assigning part of the returned json data to an array in TypeScript and Angular2

I am currently retrieving json data from a file with various fields - i am interested in 2 fields and each of the 2 field i would like to have them set to different arrays.
SERVICE file:
getCourseType(){
return this._http.get('url')
.map((res:Response) => <ICourseType[]> res.json())
.do(data =>console.log('All: ' + JSON.stringify(data)))
.catch(this.handleError);
}
COMPONENTS.ts file:
courseType: ICourseType[];
courseName: any[] = [];
courseRoster: any[] = [];
getCourseType(){ //function called from ngOnInit()
this.dataService.getCourseType().subscribe(
data => this.courseType = data,
error => this.errorMessage = <any>error);
}
JSON file:
[
{
"id": 1,
"courseTitle": "English",
"courseNumber": 340B,
"roster": 23,
},
{
"id": 2,
"courseTitle": "AP History",
"courseNumber": 1420,
"roster": 14
},
{
"id": 3,
"courseTitle": "Art",
"courseNumber": 42A,
"roster": 30
}
]
Currently i am return the json object but if i am interested in gathering all the courseTitle of each of the courses into one array and then all the roster number of each of the courses into one array - where should i do that? In the service or in my components?
In your component file
courseType: ICourseType[];
courseName: any[];
courseRoster: any[];
getCourseType(){ //function called from ngOnInit()
this.dataService.getCourseType().subscribe(
data => {
this.courseType = data
this.courseType.forEach(course=>{
this.courseName.push(course.courseTitle);
this.courseRoster.push(course.roster);
})
},
error => this.errorMessage = <any>error);
}

Categories