My First Application with ReactJS
I have no prior experience with reactjs and json, but am trying to increase my skill set and have been told reactjs is great for front end.
I am attempting to read an call an API, get a json object, parse it and display certain information. I am able to achieve almost all of this with relative ease with the help of several online tutorials, but I have hit a wall.
Issue:
I have parsed and displayed the majority of the json object but there is an array of strings in each object. I would like to take the average of that array from the json object and display that in html as well. Below is my attempt:
//importing necessary modules
import React from 'react';
import ReactDOM from 'react-dom';
//class creates the React component I am building
class ContentFeed extends React.Component {
constructor() {
super();
this.state = {
'workers': []
}
}
componentDidMount() {
this.getItems();
}
getItems() {
fetch('localhost:7575')
.then(results => results.json())
//.then(results => console.log(results));
.then(results => this.setState({'workers': results.workers}));
}
render() {
return (
<ul>
// I'm quite new to reactjs
// so I'm not too sure if what I'm doing below is legal syntax
{this.state.workers.map(function(item, index) {
var arrNum = 0;
var h = item.hours.split(",");
var sum = 0;
for(var i = 0; i < h.length; i++) {
sum += parseInt(h[i]);
}
arrNum = (sum/h.length);
return (
<div key={index}>
<h1>{item.firstName} {item.lastName}</h1>
<h4>{item.hireDate}</h4>
<h4>{item.dept}</h4>
<h4>arrNum</h4>
</div>
)
})}
</ul>
);
}
}
ReactDOM.render(
<ContentFeed/>,
document.getElementById('root')
);
What should appear is an html page with a header that contains the employees name followed by smaller headers containing the relevant employee info followed by an avg of their work hours.
However the webpage is blank.
I have checked the console tab in the developer's tools on the page and have seen an error like this:
Uncaught TypeError: item.hours.split is not a function
at index.js:29
at Array.map (<anonymous>)
at ContentFeed.render (index.js:26)
at finishClassComponent (react-dom.development.js:17160)
at updateClassComponent (react-dom.development.js:17110)
at beginWork (react-dom.development.js:18620)
at HTMLUnknownElement.callCallback (react-dom.development.js:188)
at Object.invokeGuardedCallbackDev (react-dom.development.js:237)
at invokeGuardedCallback (react-dom.development.js:292)
at beginWork$1 (react-dom.development.js:23203)
I am unsure how to resolve this. I sincerely hope I have provided as much relevant information as needed. Thank you for any help.
UPDATE 1
This is from the console tab
Uncaught TypeError: item.hours.split is not a function
at index.js:29
at Array.map (<anonymous>)
at ContentFeed.render (index.js:26)
at finishClassComponent (react-dom.development.js:17160)
at updateClassComponent (react-dom.development.js:17110)
at beginWork (react-dom.development.js:18620)
at HTMLUnknownElement.callCallback (react-dom.development.js:188)
at Object.invokeGuardedCallbackDev (react-dom.development.js:237)
at invokeGuardedCallback (react-dom.development.js:292)
at beginWork$1 (react-dom.development.js:23203)
UPDATE 2
This is some of the raw JSON from fetch
{
"workers": [
{
"firstName": "Tom",
"lastName": "Ronson",
"dept": "Sales",
"hireDate": "09/09/2015",
"hours": [
"8",
"10",
"4",
"6",
"6"
]
},
{
"firstName": "Bob",
"lastName": "Howser",
"dept": "Acct",
"hireDate": "01/05/2005",
"hours": [
"8",
"10",
"4",
"6",
"6"
]
},
{
"firstName": "Jane",
"lastName": "Winger",
"dept": "Legal",
"hireDate": "08/01/2008",
"hours": [
"5",
"6",
"5",
"5",
"6"
]
},
Your response data's hours property is an array of strings, not a comma separated list of hours as a string.
workers: [
{
firstName: "Tom",
lastName: "Ronson",
dept: "Sales",
hireDate: "09/09/2015",
hours: ["8", "10", "4", "6", "6"]
},
{
firstName: "Bob",
lastName: "Howser",
dept: "Acct",
hireDate: "01/05/2005",
hours: ["8", "10", "4", "6", "6"]
},
{
firstName: "Jane",
lastName: "Winger",
dept: "Legal",
hireDate: "08/01/2008",
hours: ["5", "6", "5", "5", "6"]
}
]
The average hours worked can be computed using an array::reduce to compute a sum of hours divided by the array length
const avg =
item.hours.reduce((sum, curr) => sum + Number(curr), 0) /
item.hours.length;
You can map the data as such (don't forget to use correct JSX syntax, i.e. <h4>{avg}</h4> versus <h4>avg</h4>)
{this.state.workers.map((item, index) => {
const avg =
item.hours.reduce((sum, curr) => sum + Number(curr), 0) /
item.hours.length;
return (
<li key={index}>
<h1>
{item.firstName} {item.lastName}
</h1>
<h4>{item.hireDate}</h4>
<h4>{item.dept}</h4>
<h4>{avg}</h4> // <-- * use correct JSX syntax
</li>
);
})}
there is an array of strings in each JSON object. I would like to take the average of that array
From your description, item.hours is an array of strings. But in your code
item.hours.split(",");
You treat it like a single, comma-separated, string.
Given you're getting TypeError, it seems it is in fact an array of strings (preferably you'd update your question with sample data).
To get the average of an array, you can use reduce:
const avg = item.hours.reduce((a,b) => a + parseFloat(b),0) / item.hours.length;
Related
I have a json file that contains content for several different pages that are under a "service" category. I use dynamic routes in nextJS by having a file as "[serviceId].tsx", this routing works. However I have a json file where I want to use the [serviceId] provided in the route to access information.
I have the following code in my [serviceId].tsx file:
const json = jsonFile.services
const router = useRouter()
const serviceId = router.query.serviceId
return (
<div>
<ArticleWithPicture title={content.title} description={content.description}/>
</div>
)
}
My json file looks similar to this (ive edited it to be more clear for this example):
{
"serviceId":
[
{
"service1": {
"id": "xx",
"title": "xxx",
"description": "xx",
"featuredCompany":
[
{ "id": "1",
"name": "xxx",
"companyPageURL": "/",
"imagePath": "xxx",
"description": "xxx",
"additionalServices": {
"service1": "xxx",
"service2": "xxx"
},
"instagramURL":"/",
"twitterURL": "/"
}
]
}
},
{
"service2": {
"id": "xxx",
"title": "xxx",
"description": "xxx",
"featuredCompany":
[
{ "id": "1",
"name": "xxx",
"companyPageURL": "/",
"imagePath": "xxx",
"description": "xxx",
"additionalServices": {
"service1": "xxx",
"service2": "xx"
},
"instagramURL":"/",
"twitterURL": "/"
}
]
}
}
]
}
Basically, each Service has the content for each indiviual page. So I want to dynamically set for instance the title of my component "ArticleWithPicture" based on the corresponding title in my json file based on the serviceId that I get from router.query.serviceId. However when I try the following code:
<ArticleWithPicture title={json.{serviceId}.title}/>
I get error (this is due to how I use "{}" within a "{}", is there a way to do this better?
But I also cannot access it if I do eg:
const title = json.serviceId.title
or (what is what I actually want to do ie: query the json file based on my serviceId provided by "router.query.serviceId")
const title = json.{serviceId}.title
I guess something might be wrong with either my json file structure or how I try to access it. Any advice would be appreciated.
Thanks!
I'm assuming the JSON you provided is your entire jsonFile.
If the JSON you provided is just jsonFile.services, then change any uses of jsonFile to jsonFile.services
and update the type.
The format of the JSON isn't great for your use case because there's a lot of unnecessary wrapping.
With your current JSON
Assuming you cannot modify the format of the JSON, you would have to find the service from the serviceId array:
function getService(json, serviceId) {
return json.serviceId
.find((serviceWrapper) => serviceWrapper[serviceId] !== undefined)
?.service;
}
A fully typed example:
type Service = {
id: string
title: string
description: string
// ...
};
// you don't have to use this, I just included it for clarity
type JsonFile = {
serviceId: {
[serviceId: string]: Service
}[]
};
function getService(json: JsonFile, serviceId: string): Service | undefined {
return json.serviceId
.find((serviceWrapper) => serviceWrapper[serviceId] !== undefined)
?.service;
}
// declare const jsonFile: JsonFile;
export default function ServicePage() {
const router = useRouter();
const serviceId = router.query.serviceId as string;
const content = getService(jsonFile, serviceId);
if (!content) return (
<div>
{'Article doesn\'t exist.'}
</div>
);
return (
<div>
<ArticleWithPicture title={content.title} description={content.description} />
</div>
);
}
With better JSON
An example JSON that would need less unwrapping is:
{
"service1": {
"id": "xx",
"title": "xxx",
// ...
},
"service2": {
"id": "xxx",
"title": "xxx",
// ...
}
}
type JsonFile = {
[serviceId: string]: Service
}
Then you would be able to just do jsonFile[serviceId] or jsonFile.services[serviceId] to get a service.
Hi I got a bit stuck at trying to understand how to fetch data of a JSON file.
environment.ts:
export const environment = {
production: false,
urlListBooks: "/assets/list-books.json",
urlGetBooks: "/assets/edit-book.json?:id",
urlGetTags: "/assets/edit-book.json?:tags",
urlPostBooks: "/assets/edit-book.json",
urlListTags: "/assets/list-tags.json",
urlPostTags: "/assets/edit-tag.json"
};
edit-book.json:
"book":{
"id": 1,
"title": "The Shining",
"authorId": 1,
"tags": [{"name":"new"}, {"name":"test"}]
},
"authors":[
{
"id": 1,
"prename": "Stephen",
"surname": "King"
},
{
"id": 3,
"prename": "Algernon",
"surname": "Blackwood"
},
{
"id": 4,
"prename": "Edgar Allan",
"surname": "Poe"
},
{
"id": 5,
"prename": "Howard Phillips",
"surname": "Lovecraft"
}
],
"tags":[
{
"name": "new"
},
{
"name": "Horror"
},
{
"name": "Romance"
}
]
}
service:
getBookTags(n: String) Observable<Tag[]>{
return this.http.get<Tag[]>(environment.urlGetTags.)
}
what I want getBookTags(n: String) to do is returning the tags array of the book with title n defined in the edit-book.json (e.g. "tags": [{"name":"new"}, {"name":"Horror"}] ) so that I can later use the function to check which tags a book has and select them.
Your help would be very appreciated :)
Ok I think I've solved this for you, I'm going to walk through my process with you so you understand what the goal is. You can see my solution here: https://codesandbox.io/s/thirsty-minsky-g6959f?file=/assets/edit-book.json:0-752
First thing is that your JSON you provided doesn't really make much sense, it shows multiple authors and just one "book". I think instead you want multiple books. Secondly, it's gotta be wrapped in a curly brace as shown:
{
"books": [
{
"id": 1,
"title": "The Shining",
"authorId": 1,
"tags": [{ "name": "new" }, { "name": "test" }]
},
{
"id": 2,
"title": "The Wendigo",
"authorId": 2,
"tags": [{ "name": "Horror" }]
}
],
"authors": [
{
"id": 1,
"prename": "Stephen",
"surname": "King"
},
{
"id": 3,
"prename": "Algernon",
"surname": "Blackwood"
},
{
"id": 4,
"prename": "Edgar Allan",
"surname": "Poe"
},
{
"id": 5,
"prename": "Howard Phillips",
"surname": "Lovecraft"
}
],
"tags": [
{
"name": "new"
},
{
"name": "Horror"
},
{
"name": "Romance"
}
]
}
Now, in your Typescript code we want to have typings for the json you're going to fetch. This will make your code more readable, it will give you intellisense, and help you catch some errors before you try to run your code. So we are going to go ahead and type the properties of the JSON as follows:
type Tag = {
name: string;
};
type Book = {
id: number;
title: string;
authorId: number;
tags: Tag[];
};
type Author = {
id: number;
prename: string;
surname: string;
};
type BookData = {
books: Book[];
authors: Author[];
tags: Tag[];
};
Basically what I said is we have bookdata which is made up of books, authors, and tags. Books have properties given under type Book, same thing with Author and Tag.
Now for the actual running code, we are going to use the fetch api to get the json data at the url.
async function getBookTags(n: string): Promise<Book[]> {
return fetch(url)
.then<BookData>((res) => res.json())
.then((data) => data.books)
.then((books) => books.filter((b) => doesBookHaveTag(b, n)));
}
First thing we do is fetch the data from the api, this returns a promise which when resolved (this is what .then does) we take the response and parse it for a json. Then when that promise resolves we get the books in the data. Then when that promise resolves we filter in books that have the matching tag.
doesBookHaveTag is just a little helper function I defined:
function doesBookHaveTag(book: Book, n: string): boolean {
// just return if book has at least one tag matching n
return book.tags.some((t) => t.name.toLowerCase() === n.toLowerCase());
}
If you don't understand promises you should watch some videos on it, but basically the browser sends out an http request and then when it resolves it queues a task to execute the function [see endnote] in .then when it has time. So when we want to call your async function and say log all books with the tag "horror" we do it as shown:
getBookTags("horror").then(console.log); // returns the one book.
I hope this makes sense and you can sort of see how to fetch the data, how to handle the promise it returns, and how to type your response. The only thing I'm not sure on is how Angular changes this for you (I'm a react guy), but this is really just non-library specific Javascript/Typescript.
[endnote] when I say function in .then, what I mean is that .then(data => data.books) is passing a function into the .then function. data => data.books is actually a function the same as:
function(data: BookData): Book[] {
return data.books
}
I would like to convert an json to tree of objects.
For example
{
"id": 2,
"label": "BEAUTY",
"description": "",
"parent_id": 0,
},
{
"id": 5,
"label": "SunGlass",
"description": "",
"parent_id": 2,
},
{
"id": 6,
"label": "Shirts",
"description": "",
"parent_id": 2,
},
{
"id": 41,
"label": "black Glasses",
"description": "electronique",
"parent_id": 5,
},
{
"id": 34,
"label": "T-shirts",
"description": "electronique",
"parent_id": 6,
},
{
"id": 3,
"label": "Phones",
"description": "",
"parent_id": 0,
"embedded_parent": null,
}
What I want is to convert this list to a tree object based on label attribute,Like this result :
const TREE_DATA = {
BEAUTY: {
'SunGlass': {'black Glasses':null},
'Shirts': null,
},
Phones: {
'Sbardilate': null,
'T-shirts': null,
'Balons': null,
},
};
I need a recursive function to make this result,to pass this result to my component widget in angular.
Thank you in advance
As #jonrsharpe mentioned, you should always provide your solution. Show us what you have done and where exactly you are stuck. It won't help you to just copy and paste solutions from StackOverflow.
For your exact problem I give you some hints. First create some Interfaces that makes the development easier. For the Node you can define a recursive interface as follows:
interface Item {
id: number,
label: string,
parent_id: number,
description: string
}
interface Node {
[label: string]: Node | null;
}
The next thing you should do is to group your elements by the parent_id. This enables you to quickly access all of the information in one Node by doing groupedByParentId[id] with an access time of O(1).
const groupedByParentId = array.reduce(
(acc, cur) => {
const parentId = cur.parentId;
const groupedItems = acc[parentId] == [];
return {...acc, [parentId]: [...groupedItems, cur]}
},
{} as {[id: string]: Item[]}
);
Defining a function for having a starting point for your tree root is a good idea as well:
const rootNodeId = 0;
function makeTree(): Node {
const node = makeNode(rootNodeId);
return node;
}
Then only the makeNode function is left. Since you haven't tried anything on your own yet, I won't provide the full solution, but here are some hints so that you can proceed hopefully easily:
The functions API is makeNode(id: number) and it returns a Node
Get the children of the current node with above data structure
If there are no children, you already know what to return ;)
If there are children, make a node for each of them and store the node in a property defined by the label
Finally return the node
Good luck! :)
I am trying to list the server response , but some mistake is their in my code about accessing nested json..Following is the structure of json
Updated:
{
"child": [],
"courses": [{
"data": {
"name": "Student 1",
"date_created": 1514610451,
"total_students": 4,
"seats": "",
"start_date": false,
"categories": [{
"name": "Subject",
"slug": "Subject"
}],
"intro": {
"id": "1",
"name": "Main Admin",
"sub": ""
},
"menu_order": 0
},
"headers": [],
"status": 200
}]
}
And my react part is
render(){
return this.state.course.map(course =>
<Text style={styles.userStyle}>{course.courses.data.map(datas => datas.name)}</Text>
);
}
Please help me to figure out the mistake.I am getting this.state.course.map is not a function.My fetch request is as follows
state= {course:[]};
componentWillMount(){
fetch('https://www.mywebsite.com/' + this.props.navigation.state.params.id)
.then((response) => response.json())
.then((responseData) => this.setState({course: responseData}))
}
So you would need to show us how this.state is set, but if you're doing something like this.setState(jsonObject), the property you are looking for seems to be this.state.courses. This would access the array of courses. However, in the subsequent lines you try to access course.courses, which suggests you're setting the state like this.seState({course: jsonObject}) so it's not clear.
I'd say if you fix the first problem, you'll immediately hit another one because it doesn't look like data is an array but an object, so trying to call map on it is unlikely to do what you want (unless you've been playing with prototypes).
EDIT:
In response to the new info, I recommend the following:
render(){
if(this.state.course && this.state.course.courses) {
return this.state.course.courses.map(course =>
<Text style={styles.userStyle}>{course.data.name}</Text>
);
} else {
return [];
}
}
Im building a React app and I have a quite complex JSON file where I need to find and output certain values of an object in an array.
Im trying to output all my people from my JSON, they look something like this:
people: [
{
"id": 1,
"email": "Sincere#april.biz",
"address": [
{
"street": "Kulas Light",
"type": "house",
"attribute": {
"sketch": "sketch.jpg",
"photo": "photo.jpg"
}
},
{
"street": "Lorem Ipsum",
"type": "apartment",
"attribute": {
"sketch": "sketch.jpg",
"photo": "photo.jpg"
}
}
]
}
]
I have no problem to output the email, doing it like so:
var App = React.createClass({
getInitialState: function () {
return {
results: {}
}
},
componentDidMount() {
fetch(REQUEST_URL) // fetch from API, returns JSON
.then(res => res.json())
.then(data => {this.setState(
{ results: data.people}
);
})
},
renderResult : function(key){
return <Result key={key} index={key} details={this.state.results[key]}/>
},
render : function() {
return (
<ul>
{Object.keys(this.state.results).map(this.renderResult)}
</ul>
)
}
});
var Result = React.createClass({
render : function() {
return (
<li>
{this.props.details.email}
<img src="{this.props.details.address.type=house.attribute.photo}"/>
</li>
)
}
});
ReactDOM.render(App, document.querySelector('#app'));
However, now I need to output "photo" but only for "type": "house". I tried this but no luck, well aware that this is way off. Im quite new to handling JSON data and React and Google hasn't helped me even after a few hours of trying to solve this.
The .address property isn't an object but an array of objects so
.type is not available directly on .address:
this.state.results.people.address.type
// .type property doesn't exist on array
Solution:
You can use Array.prototype.filter on .address to obtain an array of objects that have a property type whose value is "house":
var houseAddresses = this.state.results.people.address.filter(function(value){
return value.type === "house";
});
Here, houseAddress will be an array of objects whose type value is 'house".
You can then loop through the array to create the relevant JSX using for, Array#forEach or Array#map. The following example uses Array#map:
const houseImgTags = houseAddresses.map(function(house, index){
return (
<img
src={house.attribute.photo}
key={'house'+index}
/>
);
});
(A key was added here in case there are more than one instance of a house object)
You can simply write.
<img src={this.states.results.address.type==="house"?house.attribute.photo : otherwise_photo}/>
Basically this would compare address.type is house or not,then return the result corresponded.