Unable to read the json object stored in React state - reactjs

import React from 'react';
import axios from 'axios';
class Quiz extends React.Component {
constructor(props) {
super(props);
this.state = {
showInstruction: true,
questionIndex: 0,
isLoading: true,
questions: ''
};
}
proceedHandler = () => {
this.setState({
showInstruction: false
})
}
handleQuestion = (event) => {
event.preventDefault();
console.log('show next question');
}
componentDidMount() {
console.log("After mount! Let's load data from API...");
axios({
method: "GET",
url: "/apis/questions"
}).then(response => {
console.log(response.data);
this.setState({ questions: response.data });
this.setState({ isLoading: false });
});
}
render() {
if (this.state.showInstruction) {
return (
<div>
<h1>Welcome to Quiz</h1>
<p>Quiz instructions goes here</p>
<button type="button" onClick={this.proceedHandler}>Proceed</button>
</div>
)
}
const { isLoading, questions } = this.state;
console.log(this.state['questions'][0]);
console.log(questions[0]);
if (isLoading) {
return <div className="App">Loading...</div>;
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<div onChange={this.onChangeValue}>
{/* {questions[0]} */}
</div>
<button onClick={this.handleQuestion}>Next</button>
</form>
</div>
)
}
}
export default Quiz;
My sample API content looks like the below. Right now making the api call to local file which is stored inside Public folder. Path is public/apis/questions.
[
{
id: 0,
question: `What is the capital of Nigeria?`,
options: [`New Delhi`, `Abuja`, `Aba`, `Onisha`],
answer: `Abuja`
},
{
id: 1,
question: `What is the capital of India?`,
options: [`Punjab`, `Awka`, `Owerri`, `Enugu`],
answer: `New Delhi`
}
]
I am building a quiz app and above is my code. I try to fetch the questions from api and render them one by one based on state. I am using axios to fetch the data inside componentDidMount and I can see the this.state.questions is updated with the questions array. But when I do questions[0] or this.state.questions[0], it always returns [. Any help would be greatly appreciated as I am fairly new the react development.

The issue is from my API data. I missed to wrap the keys with double quotes.
Updating the data from api call resolved my issue. So the sample api data will look like the below.
[
{
"id": 0,
"question": "What is the capital of Nigeria?",
"options": [
"New Delhi",
"Abuja",
"Aba",
"Onisha"
],
"answer": "Abuja"
},
{
"id": 1,
"question": "What is the capital of India?",
"options": [
"Punjab",
"Awka",
"Owerri",
"Enugu"
],
"answer": "New Delhi"
}
]

Related

Pass total number of results from Yelp API call to React component

link to GitHub project: https://github.com/jkey774/codecademy-ravenous
I have been trying for a few days now to add a new feature to my little app that displays the total number of results for businesses retrieved from a Yelp API call. I can console.log(jsonResponse.total) just before mapping each business but am unsure how to set this up in the return statement to where jsonResponse.total from Yelp.js can be accessed in App.js to do something like setState({ total: total }). Do I need to make a separate API call just to get the total?
here is an example of what the response body looks like:
{
"total": 8228,
"businesses": [
{
"rating": 4,
"id": "E8RJkjfdcwgtyoPMjQ_Olg",
"review_count": 1738,
"name": "Four Barrel Coffee",
"image_url": "http://s3-media2.fl.yelpcdn.com/bphoto/MmgtASP3l_t4tPCL1iAsCg/o.jpg",
"location": {
"city": "San Francisco",
"state": "CA",
"address1": "375 Valencia St",
"zip_code": "94103"
}
},
// ...
]
}
in my Yelp.js file:
const Yelp = {
search(term, location, sortBy) {
const limit = 21;
return fetch(`https://cors-anywhere.herokuapp.com/https://api.yelp.com/v3/businesses/search?term=${term}&location=${location}&sort_by=${sortBy}&limit=${limit}`, {
headers: {
Authorization: `Bearer ${apiKey}`
}
}).then(function (response) {
return response.json();
}).then(function (jsonResponse) {
if (jsonResponse.businesses) {
return jsonResponse.businesses.map(function (business) {
return {
id: business.id,
imageSrc: business.image_url,
name: business.name,
address: business.location.address1,
city: business.location.city,
state: business.location.state,
zipCode: business.location.zip_code,
category: business.categories[0].title,
rating: business.rating,
reviewCount: business.review_count
};
});
}
});
}
};
export default Yelp;
in my App.js file
import React from 'react';
import BusinessList from './components/BusinessList/BusinessList';
import SearchBar from './components/SearchBar/SearchBar';
import Yelp from './util/Yelp';
import './App.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
total: 0,
businesses: []
}
this.searchYelp = this.searchYelp.bind(this);
}
searchYelp(term, location, sortBy) {
Yelp.search(term, location, sortBy).then((businesses) => {
this.setState({
businesses: businesses
})
})
}
render() {
return (
<div className="App">
<h1>ravenous</h1>
<SearchBar searchYelp={this.searchYelp} />
<BusinessList businesses={this.state.businesses} />
</div>
);
}
}
export default App;
Welcome to Stack Overflow. As you mentioned, you can set multiple keys in a single call to setState, so all you have to do is get the total to your App.js.
In Yelp.js you need to return the total along with the list of businesses. You could do something like:
if (jsonResponse.businesses) {
return {
total: jsonResponse.total,
businessList: jsonResponse.businesses.map(function (business) {
// ... same code as you have before
Then, rework your setState just a little bit in App.js:
Yelp.search(term, location, sortBy).then((businessData) => {
this.setState({
businesses: businessData.businessList,
total: businessData.total
})
})

DropDown not populating data from API call

I am new to React. I am trying to make an api call, and populate the data into a dropdown in react. The api call is successful and i am able to see the response also in network tab. but the response is not getting populated into the dropdown.
I want to populate "abc","def" etc from the json response onto my dropdown.
API Response
{
{
“ABC”: {
"detail": "/diagnosticWorkflows/abc”
},
“DEF”: {
"detail": "/diagnosticWorkflows/def”
},
“LMN”: {
"detail": "/diagnosticWorkflows/lmn”
},
“PQR”: {
"detail": "/diagnosticWorkflows/pqr”
}
}
UI Code
class ABC extends React.Component {
constructor(props) {
super(props);
this.state = {
diagnosticWorkflow: [],
selectedWorkflow: ""
};
}
componentDidMount(nextProps) {
api
.workflowsApi("https://localhost:8443/api/diagnostic-workflows/")
.then(res => {
let workflowFromApi = Object.keys(res).map(workflow => {
return { value: workflow, label: workflow };
});
this.setState({
diagnosticWorkflow: [
{ value: "", label: "(Select Your Workflow)" }
].concat(workflowFromApi)
});
})
.catch(error => {
Console.log(error);
});
}
render() {
return (
<div className={style.searchComponents}>
<div className={style.searchFilter}>
<label>
<span>Diagnostic Requests</span>
</label>
<Dropdown
auto={false}
source={this.state.diagnosticWorkflow}
allowBlank={false}
value={this.state.selectedWorkflow}
onChange={this.updateSearchParameters.bind(
this,
"selectedWorkflow"
)}
className={style.searchFilterDropdown}
/>
</div>
</div>
);
}
}
let workflowFromApi = Object.keys(res).map(workflow => {
return { value: workflow, label: workflow };
});
Your this code may not be working properly as res is {{....}} Object.keys(res) will be empty. Can you make the response as
{
“ABC”: {
"detail": "/diagnosticWorkflows/abc”
},
“DEF”: {
"detail": "/diagnosticWorkflows/def”
},
“LMN”: {
"detail": "/diagnosticWorkflows/lmn”
},
“PQR”: {
"detail": "/diagnosticWorkflows/pqr”
}
}
This way your Object.keys(res) will return an array of keys.

How to pass argument to function in reactjs?

How can I send sport_id form getSport to getEvents to show each sports events?
Can I put getSport function to other component, call and use it in this component?
events json:
[
{
"id": "912653",
"time": "1536471082",
"time_status": "1",
"league": {
"id": "900",
"name": "Hong Kong 2nd Division",
"cc": "hk"
},
"home": {
"id": "13767",
"name": "Yau Tsim Mong",
"image_id": "193606",
"cc": "hk"
},
"away": {
"id": "63770",
"name": "Tuen Mun SA",
"image_id": "56045",
"cc": "hk"
},
"timer": {
"tm": 74,
"ts": 25,
"tt": "1",
"ta": 0
},
"scores": {}
}
]
sports json:
[
{
"id": 8,
"name": "Rugby Union",
"is_active": null,
"slug": "rugby-union"
}
]
Here is my code:
import React, { Component } from "react";
import axios from "axios";
import moment from "moment";
export default class Feutred extends Component {
state = {
sports: [],
events: [],
isLoading: true,
errors: null
};
getSports() {
axios
.get("/api/v1/sports.json")
.then(response =>
response.data.map(sport => ({
id: sport.id,
name: sport.name,
slug: sport.slug
}))
)
.then(sports => {
this.setState({
sports,
isLoading: false
});
})
.catch(error => this.setState({ error, isLoading: false }));
}
getEvents() {
axios
.get("/api/v1/events?sport_id=${sport_id}")
.then(response =>
response.data.map(event => ({
id: event.id,
time: event.time,
league: event.league,
time_status: event.time_status,
homeTeam: event.home,
awayTeam: event.away
}))
)
.then(events => {
this.setState({
events,
isLoading: false
});
})
.catch(error => this.setState({ error, isLoading: false }));
}
componentDidMount() {
this.getSports();
(this.interval = setInterval(
() => this.getEvents({ time: Date.now() }),
12000
));
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
const { sports, isLoading } = this.state;
return (
<React.Fragment>
{!isLoading ? (
sports.map(sport => {
const { id, name } = sport;
return (
<div key={sport.id}>
<div className="text">
<p className="meta">
<span className="matchinfo">
<span className="block">time</span>
<span className="block">timestatus</span>
</span>
</p>
<h3>
home-team vs aya tream
</h3>
<p className="league">
<a className="watchlive" href="">
<span className="icon" />
<span>Watch live</span>
</a>
<span>{sport.name} - league cc - league name</span>
</p>
</div>
</div>
);
})
) : (
<p>Loading...</p>
)}
</React.Fragment>
);
}
}
Just destructure it - load sports in one component then render some <EventsLoadingComponent /> passing sport id as prop ...
HINT: Use if(isLoading) return <p>Loading...</p> in render before 'main return' - no need to use ternary operator in return JSX.
UPDATE:
render() {
const { sports, isLoading } = this.state;
if(isLoading) return <p>Loading...</p>
return (
<React.Fragment>
{sports.map(sport => <EventsLoadingComponent sport={sport}/>}
</React.Fragment>
);
}
Move getEvents into <EventsLoadingComponent/> - you'll be fething for events related to this.props.sport.id and render them. This way each of them can be separately updated.
Remember to use key in the topmost html element.
UPDATE #2:
can you please give your code comparison with my code ?
Your code - linear, procedural, 'flat template-driven', forcing async to be sync, all-in-one-component ... while html is a (flatten view of) tree structure.
React thinking (generally, not my code only) - more OO, building tree of objects closer related to data and view structure, giving them own responsibility (data handling, view). Easier to read, expand (destructure further details to components - even one-liners), suitable to decorating, easy to manage ... and reuse.
Often object in structure renders only passed children (or nothing) only providing functionality. Available level of complexity is greater, communication within this structure is far easier (and less dependent) than (it could be done) in html.
Something like this:
getEvents({ id }) {
axios
.get(`/api/v1/events?sport_id=${id}`)
...
}
componentDidMount() {
this.getSports()
.then(() => {
return Promise
.all(this.state.sports.map(this.getEvents))
});
...
}
Note:
You need to refine the way you save the data because you need to know which events are for which sport.

Displaying Data from One Component in Another

I'm learning react at the moment and I'm trying to have two components interact with each other. The hierarchy is as follows:
App
--SearchForm
--Results
There's a data object that will be filtered through a string I enter in the SearchForm component. The filtered result should be displayed in the Results component.
My logic was to have all the functions needed in the App component, and pass the data to the individual components.
I want to be able to display the filtered data in the results component.
Can anyone help me with this please?
Please find the App.js file's code below, as well as a sample of the object I'm using.
App.js
import React, { Component } from "react";
import styled from "styled-components";
import Header from "./Header";
import SearchForm from "./SearchForm";
import Results from "./Results";
import Map from "./Map";
const Outer = styled.div`
text-align:center;
`;
class App extends Component {
constructor(props) {
super(props);
this.state = {
query: "",
data: [],
refinedData: [],
};
// this.handleSearchChange = this.handleSearchChange.bind(this);
}
handleSearchChange = (event) => {
this.setState({
query: event.target.value,
});
}
getData = async () => {
const response = await fetch("http://localhost:4200/bookings");
const json = await response.json();
this.setState({
data: json,
})
console.log(this.state.data);
}
filterData = () => {
const filtered = this.state.data.filter(element => {
return element.toLowerCase().includes(this.state.query.toLowerCase());
});
this.setState({
refinedData: filtered,
});
console.log(this.state.refinedData);
}
componentDidMount() {
this.getData();
}
render() {
return (
<Outer>
<Header/>
<SearchForm triggeredUpdate={this.handleSearchChange}/>
<Results searchQuery={this.state.filterData}/>
<Map/>
</Outer>
);
}
}
export default App;
Object
[
{
"id": 50000,
"car": {
"id": 1000,
"licence_plate": "SKK5050Q"
},
"book_start": 1543271643,
"book_end": 1543340723,
"pickup": {
"id": 87,
"code": "WDL",
"lat": 1.434,
"lng": 103.78
},
"dropoff": {
"id": 85,
"code": "TPY",
"lat": 1.33,
"lng": 103.851
},
"user": {
"id": 51498,
"name": "Count Dooku"
}
}
]
This is a simple logic actually in React. You want to show filtered results in your Results component, then you pass the filtered state to it. You can trigger the search with a button, then maybe the suitable place for this can be Search component. For this, you will pass your filterData method to it as a prop as you think.
I said a few times "it is an array not object" in my comments since the last data you show in your question says Object as bold but it is an array :) So, I got confused but you are doing it right.
You should filter your data with a prop in your object. As you think again, like user.name, car.license_late etc. You need a target here.
Here is a simple working example:
class App extends React.Component {
state = {
query: "",
data: [
{
"id": 50000,
"car": {
"id": 1000,
"licence_plate": "SKK5050Q"
},
"book_start": 1543271643,
"book_end": 1543340723,
"pickup": {
"id": 87,
"code": "WDL",
"lat": 1.434,
"lng": 103.78
},
"dropoff": {
"id": 85,
"code": "TPY",
"lat": 1.33,
"lng": 103.851
},
"user": {
"id": 51498,
"name": "Count Dooku"
}
}
],
refinedData: [],
};
handleSearchChange = event => this.setState({
query: event.target.value,
});
filterData = () => {
const { data, query } = this.state;
const filtered = !query ? [] : data.filter(element =>
element.car.licence_plate.toLowerCase().includes(this.state.query.toLowerCase())
);
this.setState({
refinedData: filtered,
});
}
render() {
return (
<div>
<SearchForm filterData={this.filterData} triggeredUpdate={this.handleSearchChange} />
<Results refinedData={this.state.refinedData} />
</div>
);
}
}
const Results = props => (
<div>
{
props.refinedData.map( el =>
<div key={el.id}>
<p>ID: {el.id}</p>
<p>User name: {el.user.name}</p>
</div>
)
}
</div>
)
const SearchForm = props => (
<div>
<input onChange={props.triggeredUpdate} />
<br />
<button onClick={props.filterData}>Search</button>
</div>
)
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Update after discussion on chat
You can do the search without a button while you typing. We don't have filterData method anymore since we moved the filter logic into handleSearchChange method. Also, we don't need any query state right now.
filterData array created with a ternary operator. If there is no search value we are returning an empty array since we don't want to list all of our data if there is not any search. By the way, I've updated my previous solution according to that, too. It was returning all the data if we hit the Search button with an empty input.
class App extends React.Component {
state = {
data: [
{
"id": 50000,
"car": {
"id": 1000,
"licence_plate": "SKK5050Q"
},
"book_start": 1543271643,
"book_end": 1543340723,
"pickup": {
"id": 87,
"code": "WDL",
"lat": 1.434,
"lng": 103.78
},
"dropoff": {
"id": 85,
"code": "TPY",
"lat": 1.33,
"lng": 103.851
},
"user": {
"id": 51498,
"name": "Count Dooku"
}
}
],
refinedData: [],
};
handleSearchChange = event => {
const { value: query } = event.target;
this.setState(prevState => {
const filteredData = !query ? [] : prevState.data.filter(element =>
element.car.licence_plate.toLowerCase().includes(query.toLowerCase())
);
return {
refinedData: filteredData
};
});
}
render() {
return (
<div>
<SearchForm triggeredUpdate={this.handleSearchChange} />
<Results refinedData={this.state.refinedData} />
</div>
);
}
}
const Results = props => (
<div>
{
props.refinedData.map(el =>
<div key={el.id}>
<p>ID: {el.id}</p>
<p>User name: {el.user.name}</p>
</div>
)
}
</div>
)
const SearchForm = props => (
<div>
<input onChange={props.triggeredUpdate} />
</div>
)
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Uncaught TypeError - issue mapping over json object in React

I have a react component which is pulling in user data, and should be able to display one of the values from the JSON object. However, I'm getting the following Uncaught TypeError: this.state.reasons.map is not a function error. I believe its because it is expecting an array vs an object, but not quite sure how to check or convert as needed. It should be able to map over the object and render the value of subscription.current_period_end
This is the JSON object:
{
"displayName": "username",
"email": "user#email.com",
"entitled": true,
"id": "23456789",
"subscription": {
"canceled_at": 1508519952,
"current_period_end": 1524765490,
"is_premium": true,
"is_renewing": false,
"plan_type": "Annual",
"saved": true,
"status": "Active"
},
"country": "us",
"language": "en"
}
React Component
class CancelConfirm extends React.Component {
constructor(props) {
super(props)
this.state = {
reasons: []
}
this.processData = this.processData.bind(this)
}
componentDidMount() {
this.fetchContent(this.processData)
}
fetchContent(cb) {
superagent
.get('/api/user')
.then(cb)
}
processData(data) {
this.setState({
reasons: data.body
})
}
render(props) {
const content = this.props.config.contentStrings
const reason = this.state.reasons.map((reason, i) => {
return (
<p key={i}>{reason.subscription.current_period_end}</p>
)
})
console.log(reason)
return (
<div className = 'confirm' >
<p className = 'confirm-subpara' >{reason}</p>
</div>
)
}
}
export default CancelConfirm
Your this.state.reasons is response object from JSON, object doesn't have map function it's only for iterator values such as new Array() if you are trying to show reason you would simply do
Currently I don't know why you are trying to iterate over object when it has no array array data, maybe I got your question wrong so please specify why you even did map instead of simple object reference.
var API_RESPONSE = {
"body": {
"displayName": "username",
"email": "user#email.com",
"entitled": true,
"id": "23456789",
"subscription": {
"canceled_at": 1508519952,
"current_period_end": 1524765490,
"is_premium": true,
"is_renewing": false,
"plan_type": "Annual",
"saved": true,
"status": "Active"
},
"country": "us",
"language": "en"
}
}
class CancelConfirm extends React.Component {
constructor(props) {
super(props)
this.state = {
reasons: false
}
this.processData = this.processData.bind(this)
}
componentDidMount() {
this.fetchContent(this.processData)
}
fetchContent(cb) {
setTimeout(() => {
cb(API_RESPONSE)
}, 2000)
}
processData(data) {
this.setState({
reasons: data.body
})
}
render(props) {
if (!this.state.reasons) return (<i>Loading ....</i>)
const reason = <p>{this.state.reasons.subscription.current_period_end}</p>
return (
<div className = 'confirm' >
<p className = 'confirm-subpara' >{reason}</p>
</div>
)
}
}
ReactDOM.render(<CancelConfirm />, document.getElementById('react'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="react"></div>
You can't map() over an Object. As such it would be better to do something like this:
render(props) {
const content = this.props.config.contentStrings
const { reasons } = this.state;
const keys = Object.keys(reasons);
return (
keys.map((k, i) => {
return <p key={i}>{reasons[k].subscription.current_period_end}</p>
})
)
}

Resources