I'm trying to loop through an array of objects inside of Firebase, in my case I'm trying to access the data in stats, and I'm not sure how to access that value, I'm trying to use map but its giving me an error saying:
cannot read property map of undefined
Code:
// Champs
// -LIvNqyUt8Bsvrspears
// id:
// "-LIvNqyUt8Bsvrspears"
// img:
// "https://vignette.wikia.nocookie.net/leagueofleg..."
// img2:
// "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQAB..."
// name:
// "Ronaldo"
// Stats
// lvl: "medium"
// "win rate ": "51%"
// Team: "real madrid"
import React, { Component } from "react";
import { ChampsRef, timeRef } from "./reference";
import { getsinglechamp } from "../actions/champs";
import { connect } from "react-redux"; // this is not being used. oh isee so like this?
import { Redirect, Link } from "react-router-dom";
class OneChamp extends Component {
state = {
name: "",
weak: [],
img: ""
};
componentWillMount() {
const { dispatch, match } = this.props;
dispatch(getsinglechamp(match.params.id));
console.log(this.props);
}
render() {
console.log(this.props.champ);
const { dispatch, loading } = this.props;
console.log("change", this.props);
console.log(this.props.champ.stats);
let content = null;
if (loading) {
content = <p>Loading...</p>;
} else {
content = (
<div>
<div>
<h1>{this.props.champ.name}</h1>
<img src={this.props.champ.img} height="80px" />
</div>
<br />
<ul>
{this.props.champ.stats.map(stats => (
<div>
<li>{stats.lvl} </li>
</div>
))}
</ul>
</div>
);
}
return <div>{content}</div>;
}
}
const mapStateToProps = state => {
console.log("champs", state.champs);
console.log(state.loading);
return {
champ: state.champs.champ,
loading: state.loading
};
};
export default connect(
mapStateToProps,
null
)(OneChamp);
It's hard to be certain without seeing how you initialize this.props.champ.stats, but my guess is that this is a DataSnapshot you get from the Firebase Database.
While a DataSnapshot may look like an array at a glance, it does not implement Array.map(). It does however implement forEach() so you could use that to loop over the items, and then render each individually to HTML.
Related
I am trying to send API data being called from my index.js to my ChartData.js. index.js is a page and ChartData.js is a component in Gatsby, so to begin with I could not figure out if Gatsby's Link to="" function only work from one page to another or if it can send data from a page to a component.
The issue is when I try to access the sent data from index.js to ChartData.js in the line {props.location.state.companyName} I am getting the error: TypeError: props.location is undefined
I plan to switch out labels: ['x', 'y'] for something like labels: [{props.location.state.companyName}, {props.location.state.symbol} etc. I am not sure if this would be the correct syntax either.
A more detailed explanation here: https://www.youtube.com/watch?v=No9cqzqlKS0&feature=youtu.be
index.js:
import React from "react"
import { Link } from "gatsby"
import axios from "axios"
import "../css/style.css"
import Layout from "../components/layout"
import { symbol } from "prop-types"
import ChartData from "../components/ChartData"
export default class index extends React.Component {
state = {
companyName: "",
previousClose: "",
marketCap: "",
change: "",
symbol: "",
topStocks: [],
Yearweekhigh: "",
Yearweeklow: "",
avgTotalVolume: "",
peRatio: ""
}
componentDidMount() {
const API_KEY = '*******************';
axios.get(`https://cloud.iexapis.com/stable/stock/market/previous?token=${API_KEY}`)
.then(res => {
console.log(res)
const topStocks = res.slice(1);
this.setState({ topStocks })
})
}
clickHandler = (event) => {
if (event.keyCode === 13) {
const query = event.target.value;
const API_KEY = '*******************';
axios.get(`https://cloud.iexapis.com/stable/stock/${query}/quote?token=${API_KEY}`)
.then(res => {
const companyName = res.data['companyName'];
this.setState({ companyName })
const previousClose = res.data['previousClose'];
this.setState({ previousClose })
const marketCap = res.data['marketCap'];
this.setState({ marketCap })
const change = res.data['change'];
this.setState({ change })
const symbol = res.data['symbol'];
this.setState({ symbol })
const Yearweekhigh = res.data['week52High'];
this.setState({ Yearweekhigh })
const Yearweeklow = res.data['week52Low'];
this.setState({ Yearweeklow })
const avgTotalVolume = res.data['avgTotalVolume'];
this.setState({ avgTotalVolume })
const peRatio = res.data['peRatio'];
this.setState({ peRatio })
})
}
}
render() {
return (
<Layout>
<div class = "main-div">
<input type="search" class="main-search" onKeyDown={event => this.clickHandler(event)}/>
<table>
<tr>
<th>Ticker-Symbol</th>
<th>Market Cap</th>
<th>Previous Close</th>
</tr>
<tr>
<td>
<Link to='/details/' state={{
setState: this.state.symbol,
companyName: this.state.companyName,
previousClose: this.state.previousClose,
marketCap: this.state.marketCap,
change: this.state.change,
Yearweekhigh: this.state.Yearweekhigh,
Yearweeklow: this.state.Yearweeklow,
avgTotalVolume: this.state.avgTotalVolume,
peRatio: this.state.peRatio
}}>
{this.state.symbol}</Link>
<Link to='/ChartData/' state={{
setState: this.state.symbol,
companyName: this.state.companyName,
previousClose: this.state.previousClose,
marketCap: this.state.marketCap,
change: this.state.change,
Yearweekhigh: this.state.Yearweekhigh,
Yearweeklow: this.state.Yearweeklow,
avgTotalVolume: this.state.avgTotalVolume,
peRatio: this.state.peRatio
}}></Link>
</td>
<td>{this.state.marketCap}</td>
<td>{this.state.previousClose}</td>
</tr>
</table>
</div>
<div>
{
this.state.topStocks.length && this.state.topStocks.map(stock => (
<h1>{stock.symbol}</h1>
))
}
</div>
<ChartData />
</Layout>
)
}
}
details.js
//import { Link } from "gatsby"
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import Layout from '../components/layout';
import "../css/style.css"
const Details = props => {
const [yourState, setYourState] = useState('');
useEffect(() => {
}, []);
return <Layout>
<div>
<h1 class="details-company-name">{props.location.state.companyName}</h1>
<div class = "details-div">
<div class="details-div-1">
<p>Open {} </p>
<p>High {} </p>
<p>Low {} </p>
<p>52 WK HIGH <h2>{props.location.state.Yearweekhigh}</h2> </p>
<p>52 WK LOW <h2>{props.location.state.Yearweeklow}</h2> </p>
</div>
<div class="details-div-2">
<p>VOLUME</p>
<p>AVG VOL <h2>{props.location.state.avgTotalVolume}</h2> </p>
<p>MKT CAP <h2>{props.location.state.marketCap}</h2></p>
<p>P/E RATIO <h2>{props.location.state.peRatio}</h2></p>
<p>DIV/YIELD</p>
</div>
</div>
</div>
</Layout>;
};
export default Details;
ChartData.js
import React, {useState, useEffect } from "react";
import { Line } from "react-chartjs-2";
const ChartData = props => {
const [yourState, setYourState] = useState('');
const chart = () => {
setYourState({
labels: ['x', 'y'],
datasets: [
{
level: 'level of xyz',
data: [22, 55]
}
]
})
}
useEffect(() => {
chart()
}, [])
return(
<div>
<h1>Hello</h1>
{props.location.state.companyName}
<div>
<Line data={yourState}/>
</div>
</div>
)
}
export default ChartData;
There's a quite a bit going on here that needs clarification. You mention graphql in the title, but there's no graphql in your code.
You are using axios to fetch data at runtime in the componentDidMount lifecycle method, and then setting the result to state.
I assume that once you have that data, all you want to do is pass it to your chart component so that it can render itself on the index page.
Consider the following example which does the same thing; Fetches some data from the Rick & Morty api, sets the results to state, and passes the relevant part of that state via props directly to the <Characters /> component.
From there, the <Characters /> component has everything it needs in order to render. (It has no state, and is not concerned about where the data actually came from).
// index.js
import React from 'react';
import './App.css';
import Characters from './Characters'
const api = "https://rickandmortyapi.com/api/character/";
class IndexPage extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: []
};
}
componentDidMount() {
fetch(api)
.then(res => res.json())
.then(
json => {
console.log(json)
this.setState({
isLoaded: true,
data: json.results
});
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
error => {
this.setState({
isLoaded: true,
error
});
}
);
}
render() {
const { error, isLoaded, data } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<Characters data={data} />
);
}
}
}
export default IndexPage;
// Characters.js
import React from 'react';
class Characters extends React.Component {
render() {
return (
<ul>
{this.props.data.map(item => (
<li key={item.id}>
<dl>
<dt>Name:</dt>
<dd>{item.name}</dd>
<dt>Species:</dt>
<dd>{item.species}</dd>
<dt>Status:</dt>
<dd>{item.status}</dd>
</dl>
</li>
))}
</ul>
);
}
}
export default Characters;
Codesandbox Example using functional components and hooks
Gatsby’s <Link> component allows you to link between pages (and does some other stuff like prefetching resources, and can share data between pages). As you are rendering the <ChartData /> component on the index page, this is not required to solve your problem.
Using <Link> with state works because details is a gatsby page. As <ChartData> is not a page, you can't *link* to it.
Slight issue here which I think is relatively simple to solve but I can't quite get my head around. I'm quite new to React. I've decided to make a small sample app which just takes the input from two fields, saves them to Firebase and outputs those values on the page. It works completely fine in terms of submitting data and retrieving it, but when I click the submit button to add the data to Firebase it seems to duplicate the data stored in the state and render them twice:
Parent Component:
import React, { Component, Fragment } from 'react';
import firebase from '../../config/firebase';
import QuestFormField from './QuestFormField/QuestFormField';
import QuestFormSelection from './QuestFormSelection/QuestFormSelection';
import classes from './QuestForm.css';
class QuestForm extends Component {
state = {
value: '',
points: 0,
items: []
}
questHandler = e => {
this.setState({
value: e.target.value,
});
}
pointsHandler = e => {
this.setState({
points: e.target.value,
});
}
submitHandler = e => {
e.preventDefault();
const itemsRef = firebase.database().ref('quest');
const items = {
quest: this.state.value,
points: this.state.points
}
itemsRef.push(items);
this.setState({
value: '',
points: 0
});
}
render () {
return (
<Fragment>
<form className={classes.Form} onSubmit={this.submitHandler}>
<QuestFormField val='Quest' inputType='text' name='quest' value={this.state.value} changed={this.questHandler} />
<QuestFormField val='Points' inputType='number' name='points' value={this.state.points} changed={this.pointsHandler} />
<button>Away! To Firebase!</button>
</form>
<QuestFormSelection />
</Fragment>
);
}
}
export default QuestForm;
Child Component (Form Fields)
import React from 'react';
import classes from './QuestFormField.css';
const QuestFormField = (props) => (
<div className={classes.Container}>
<label htmlFor={props.name}>{props.val}</label>
<input type={props.inputType} name={props.name} onChange={props.changed}/>
</div>
);
export default QuestFormField;
Child Component B (Data Retriever/Displayer)
import React, { Component, Fragment } from 'react';
import firebase from '../../../config/firebase';
import classes from './QuestFormSelection.css';
class QuestFormSelection extends Component {
state = {
quests: []
}
componentDidMount() {
const database = firebase.database();
const quests = [];
database.ref('quest').on('value', (snapshot) => {
snapshot.forEach((childSnapshot) => {
quests.push({
id: childSnapshot.key,
quest: childSnapshot.val().quest,
points: childSnapshot.val().points,
});
});
console.log(quests);
this.setState(() => {
return {
quests: quests
}
});
console.log(this.state.quests);
});
}
render () {
return (
<section className='display-item'>
<div className="wrapper">
{this.state.quests.map(quest => (
<div key={quest.key}>
<p>{quest.quest}</p>
<p>{quest.points}</p>
</div>
))}
</div>
</section>
)
}
}
export default QuestFormSelection;
Example of behaviour here:
https://i.gyazo.com/c70972f8b260838b1673d360d1bec9cc.mp4
Any pointers would help :)
I haven't used firebase myself, but it looks like the code below is setting up a listener to "quest" changes which will execute each time a change occurs, but you defined const quests = [] outside of the db change handler. This means that on the second change, you will push everything in the snapshot to the same quests array that may have already had previous snapshots added to it. I believe you can fix this by moving the quests variable inside the listener function as shown below.
componentDidMount() {
const database = firebase.database();
database.ref('quest').on('value', (snapshot) => {
const quests = [];
snapshot.forEach((childSnapshot) => {
quests.push({
id: childSnapshot.key,
quest: childSnapshot.val().quest,
points: childSnapshot.val().points,
});
});
console.log(quests);
this.setState(() => {
return {
quests: quests
}
});
console.log(this.state.quests);
});
}
In my react application, I am passing my data from parent to child as props. In my child component, I am able to see the data in props however when I try to access the data, I am getting an error saying "cannot read property of undefined".
I have written my child component like below-
Child Component-
import React from 'react';
import ReactDOM from 'react-dom';
import { setData } from '../actions/action'
import { connect } from 'react-redux'
import {
Accordion,
AccordionItem,
AccordionItemTitle,
AccordionItemBody,
} from 'react-accessible-accordion';
import 'react-accessible-accordion/dist/fancy-example.css';
import 'react-accessible-accordion/dist/minimal-example.css';
const ChildAccordion = (props) => {
console.log(props);
return (
<Accordion>
<AccordionItem>
<AccordionItemTitle>
<h3> Details:
{ props?
props.map(d =>{
return <span>{d.key}</span>
})
:
""
}
</h3>
<div>With a bit of description</div>
</AccordionItemTitle>
<AccordionItemBody>
<p>Body content</p>
</AccordionItemBody>
</AccordionItem>
</Accordion>
)
};
export default ChildAccordion
Parent Component-
import React from 'react';
import ReactDOM from 'react-dom';
import ChildAccordion from './ChildAccordion'
import { setData } from '../actions/action'
import { connect } from 'react-redux'
import {
Accordion,
AccordionItem,
AccordionItemTitle,
AccordionItemBody,
} from 'react-accessible-accordion';
import 'react-accessible-accordion/dist/fancy-example.css';
import 'react-accessible-accordion/dist/minimal-example.css';
class ParentAccordion extends React.Component {
componentWillMount() {
//call to action
this.props.setData();
}
getMappedData = (dataProp) =>{
if (dataProp) {
let Data = this.props.dataProp.map(d =>{
console.log(d);
})
}
}
render(){
const { dataProp } = this.props;
return (
// RENDER THE COMPONENT
<Accordion>
<AccordionItem>
<AccordionItemTitle>
<h3>Policy Owner Details:
{ dataProp?
dataProp.map(d =>{
return <span>{d.key1}</span>
})
:
""
}
</h3>
</AccordionItemTitle>
<AccordionItemBody>
<ChildAccordion {...dataProp} />
</AccordionItemBody>
</AccordionItem>
</Accordion>
);
}
}
const mapStateToProps = state => {
return {
dataProp: state.dataProp
}
};
const mapDispatchToProps = dispatch => ({
setData(data) {
dispatch(setData(data));
}
})
export default connect (mapStateToProps,mapDispatchToProps) (ParentAccordion)
I am using map function inside as my api response can be array of multiple objects.
Once you know what the prop that you're passing in is called, you can access it like so from within your child component: {props.data.map(item => <span>{item.something}</span>}
const Parent = () => {
return (
<Child data={[{ id: 1, name: 'Jim' }, { id: 2, name: 'Jane ' }]} />
);
}
const Child = (props) => {
return (
<ul>
{props.data.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
You are passing dataProp down to ChilAccordian as a prop. So in Child component you should access it using props.dataProp and do map on props.dataProp but not on props directly
ChildAccordian:
<h3> Details:
{ Array.isArray(props.dataProp) && props.dataProp.length > 0 ?
props.dataProp.map(d =>{
return <span key={d.id}>{d.key}</span>
})
:
""
}
</h3>
Also keep in mind that you have to add unique key to parent Jsx element when you generate them in loop like for loop, .map, .forEach, Object.keys, OBject.entries, Object.values etc like I did in the above example. If you don’t get unique id from the data then consider adding index as unique like
<h3> Details:
{ Array.isArray(props.dataProp) && props.dataProp.length > 0 ?
props.dataProp.map((d, index) =>{
return <span key={"Key-"+index}>{d.key}</span>
})
:
""
}
</h3>
Edit: If it is an object then do something like below and regarding using a method to generate jsx elements
getMappedData = dataProp =>{
if(props.dataProp){
Object.keys(props.dataProp).map(key =>{
return <span key={"Key-"+key}>{props.dataProp[key]}</span>
});
}else{
return "";
}
}
<h3> Details:
{this.getMappedData(props.dataProp)}
</h3>
When I replace "this.props.fetchInfo(recipe.id)" with console.log(recipe.id), the "recipe.id" is logged when the div is clicked. However, the action is never fired. I have another console.log in my actual action creator that would log the id if it is actually passed in, but I just get an error (which I expect, but I figure I would at least get the id logged before the errors in my console). I'm not sure if the structure is set up correctly for the "Recipe" component although it appears as it is. Also, I'm not sure if there is a more correct way to do what I'm trying to do, which is to take in a piece (recipe.id) of the state from my last AJAX request and use it to make a second AJAX request to return the info I need. The following is the Recipe component:
import React, { Component } from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { fetchInfo } from "../actions";
// Here we take in the passed in props from the FoodList component and render
// the dishes for the inputted ingredients
class Recipe extends Component {
renderFood(food) {
return (
<div className="food-container">
{food.map(function(recipe) {
console.log(recipe.id);
return (
<div
className="indiv-recipe"
style={{
backgroundImage: "url(" + recipe.image + ")"
}}
onClick={() => this.props.fetchInfo(recipe.id)}
>
<div id="recipe-title"> {recipe.title}</div>
</div>
);
})}
</div>
);
}
render() {
return (
<div>
<h1>{this.props.foods.map(this.renderFood)}</h1>
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchInfo }, dispatch);
}
export default connect(
null,
mapDispatchToProps
)(Recipe);
Well, I coded something I believe you are trying to do.
class Recipe extends React.Component {
renderFood(foods) {
return (
<div>
{foods.map(recipe => (
<div
key={recipe.id}
onClick={() => this.props.fetchInfo(recipe.id)}
>
<div id="recipe-title">{recipe.title}</div>
</div>
)
)}
</div>
);
}
render() {
const { foods } = this.props;
return (
<div>
{this.renderFood(foods)}
</div>
);
}
}
const foodsList = [
{ title: "beans", id: 1 },
{ title: "rice", id: 2 },
];
class Main extends React.Component {
state = { received: false, info: null };
fetchInfo(id) {
this.setState({
received: true,
info: id,
});
}
render() {
const { received, info } = this.state;
return (
<div>
{received && (<div>{info}</div>)}
<Recipe
foods={foodsList}
fetchInfo={(id) => this.fetchInfo(id)}
/>
</div>
);
};
}
ReactDOM.render(<Main />, document.querySelector("#app"));
<div id="app"></div>
<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>
You are binding in the wrong way.
Replace
{food.map(function(recipe) {
with
{food.map(recipe => {
I'm making a movie search page. When I search something, it goes through the data base and find the very first match and display on the page. However, I want to create a function, so when I click next, page displays next movie in the data base. My code follows:
import React, { Component, PropTypes } from 'react';
import SearchBar from './Bar/index.js';
import SearchResult from './Result/index.js';
import axios from 'axios';
import './index.css';
class SearchArea extends Component {
constructor(props) {
super(props);
this.state = {
searchText: '',
searchResult: {},
result: false,
count: 0
};
}
handleSearchBarChange(event) {
this.setState({searchText: event.target.value});
}
handleSearchBarSubmit(event) {
event.preventDefault();
const movie = this.state.searchText;
axios.get(`https://api.themoviedb.org/3/search/movie?api_key=c6cd73ec4677bc1d7b6560505cf4f453&language=en-US&query=${movie}&page=1&include_adult=false`)
.then(response => {
if(response.data.results.length >= 0) {
const i = 0;
const {
title,
overview,
release_date: releaseDate
} = response.data.results[this.state.count];
const posterPath = 'https://image.tmdb.org/t/p/w154' + response.data.results[this.state.count].poster_path;
this.setState({
searchResult: {
title,
posterPath,
overview,
releaseDate
},
result: true
});
}
else {
this.setState({
searchResult: {
title: 'No Result',
overview: 'No Overview Available',
posterPath: ''
},
result: true
});
}
})
}
handleSearchNext(event) {
this.handelSearchBarSubmit.overview = response.data.results[1];
}
handleResultClose() {
this.setState({
searchResult: {},
result: false
});
}
render() {
return (
<div>
<SearchBar
value = {this.state.searchText}
onChange = {this.handleSearchBarChange.bind(this)}
onSubmit = {this.handleSearchBarSubmit.bind(this)}
onNext = {this.handleSearchNext.bind(this)}
/>
{this.state.result &&
<SearchResult
searchResult = {this.state.searchResult}
onClose = {this.handleResultClose.bind(this)}
onAdd = {this.props.onAdd}
/>
}
</div>
);
}
}
SearchArea.propTypes = {
onAdd: PropTypes.func.isRequired
};
export default SearchArea;
I can't seem to figure out how to make handleSearchNext. Please help
EDIT
Following is the SearchBar code
import React, { PropTypes } from 'react';
import { Button } from 'semantic-ui-react';
import styles from './index.css';
const SearchBar = (props) => {
return (
<div>
<form onSubmit={(event) => props.onSubmit(event)}>
<input
className="searchBar"
type="text"
placeholder="Search Here"
value={props.value}this
onChange={(event) => props.onChange(event)}
onNext={(event) => props.onChange(event)}
onBack={(event) => props.onChange(event)}
/>
<Button className="button" type="submit">Sumbit</Button>
</form>
<Button className={styles.button} type="previous">Back</Button>
<Button className="button" type="next">Next</Button>
</div>
);
};
SearchBar.propTypes = {
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
onBack: PropTypes.func.isRequired,
onNext: PropTypes.func.isRequired
};
export default SearchBar;
You could have your server respond with not only the requested title, but also the next one. That way, when you click on Next, you can immediately display the next movie without waiting for a response, while still querying it in the background by name or id (so that you have the next after it, etc.).
Edit: If I misunderstood what you meant and you already have this (it looks like you are actually querying a whole page of movies at once), you probably simply want something like
handleSearchNext(event) {
this.setState({ searchResult: response.data.results[1], result: true });
}
and handle specially the case when you hit the last item on the page.