Map over API data in React - javascript

I am trying to map over an API that is set up like the following picture:
Currently my code is set up so that I can call the headline, source, and image of which ever number I choose. I.e. if i do
const headline = res.data[0]['headline'];
this.setState({ headline })
I can get the first headline, under the name '0'
const headline = res.data[1]['headline'];
this.setState({ headline })
Then I can get the second headline, under the name '1'.
But rather than writing the same code 5-10 times, I am trying map through it so that I can return each version sliced to how many I want. I do not know how to setup the syntax, as I thought I would do something like:
<h1>
{
res.data.length && res.data.map(data => (
{data.headline}
))
}
</h1>
I got res.data is not defined as I did this in the return and not under getStock, not sure how to set this up.
News.js
import React, { Component } from 'react'
import axios from "axios"
export class News extends React.Component {
constructor(props) {
super(props);
this.state = {
headline: "",
source: "",
image: ""
}
}
componentDidMount() {
this.getStock();
}
getStock() {
const API_KEY = '*********************';
const API_CALL = `https://cloud.iexapis.com/stable/stock/aapl/news?token=${API_KEY}`;
axios.get(API_CALL)
.then(res => {
const headline = res.data[1]['headline'];
this.setState({ headline })
const source = res.data[1]['source'];
this.setState({ source })
const image = res.data[1]['image'];
this.setState({ image })
})
}
render() {
return (
<div>
<h1>{this.state.headline} </h1>
<p>{this.state.source}</p>
<img src={this.state.image} alt="img" />
</div>
)
}
}
export default News

You fist approach using .map was right you just need to make sure to check if res exists.
res && res.data.length && res.data.map(data => ...

Related

ReactDOM renders only one component

I want to create an app with comments feature. I am trying with the code like this:
response.data.forEach((el, idx, arr) => {
const newMessage = <CommentMessage username={el.username} message={el.message}/>
ReactDOM.render(newMessage, this.commentListRef.current)
})
I am using MySQL. Axios for HTTP Requests. And Next.js for the framework.
Full code:
import React from 'react'
import ReactDOM from 'react-dom'
import styles from './comments-list.module.css'
class CommentMessage extends React.Component {
constructor(props) {
super(props)
}
render() {
return (<div>
<b>{this.props.username}</b>
<span>: </span>
<span>{this.props.message}</span>
</div>)
}
}
class CommentsList extends React.Component {
constructor(props) {
super(props)
this.commentListRef = React.createRef()
const comments = []
}
loadComments() {
const axios = require('axios')
axios.get('/api/getcomments')
.then(response => {
response.data.forEach((el, idx, arr) => {
const newMessage = <CommentMessage username={el.username} message={el.message}/>
ReactDOM.render(newMessage, this.commentListRef.current)
})
})
.catch(err => {
console.log(err)
})
}
render() {
return (<div ref={this.commentListRef} onLoad={this.loadComments()}>
</div>)
}
}
export default CommentsList
But it only render this:
Expected this:
You're going about this pretty strangely; I don't know if that's on purpose or not. Regardless, the recommended approach would be to store the comments as part of your component's state, and update the state when you get the comments.
Like this:
class CommentsList extends React.Component {
constructor(props) {
super(props)
this.state = {
comments: []
};
this.commentListRef = React.createRef()
const comments = []
}
loadComments() {
const axios = require('axios')
axios.get('/api/getcomments')
.then(response => {
this.setState({
comments: response.data
});
})
.catch(err => {
console.log(err)
})
}
componentDidMount(){
this.loadComments();
}
render() {
return (<div ref={this.commentListRef}>
(this.state.comments.map(comment => (
<CommentMessage username={comment.username} message={comment.message}/>
)))
</div>)
}
}
Also, your onLoad wasn't working as you had expected. It will call loadComments every time the component renders, and I don't even know if onLoad is a proper event on a div.
At any rate, if you absolutely wanted to do it the way you did it, you would have to mount each node into its own container. As you have it right now, each comment is overwriting the contents of commentListRef. So you'd have to create a new element, append that to commentListRef, and mount the react component to that:
loadComments() {
const axios = require('axios')
axios.get('/api/getcomments')
.then(response => {
response.data.forEach((el, idx, arr) => {
const element = document.createElement('div');
this.commentListRef.current.appendChild(element);
const newMessage = <CommentMessage username={el.username} message={el.message}/>
ReactDOM.render(newMessage, element)
})
})
.catch(err => {
console.log(err)
})
}
ReactDOM.render will only render one component for a given container. From the docs:
Any existing DOM elements inside are replaced when first called. Later calls use React’s DOM diffing algorithm for efficient updates.
Basically when you call ReactDOM.render in a loop, React is treating each given component as an update to the previous component, rather than rendering each individually.
Best practice is to render a single component at the root container (usually called <App>). However it seems you've already done this as these ReactDOM.render calls are happening within another component. Generally, you should only need to use ReactDOM.render once within an app.
Instead you can store the data in the CommentsList component's state and just return the children components from the parent's render method.
For example:
class CommentsList extends React.Component {
constructor(props) {
super(props)
this.state = {
comments: [],
}
}
loadComments = () => {
const axios = require('axios')
axios.get('/api/getcomments')
.then(response => {
this.setState(prev => ({...prev, comments: response.data}));
})
.catch(err => {
console.log(err)
})
}
render() {
const { comments } = this.state;
return (
<React.Fragment>
{comments.map(e => (
<CommentMessage key={e.id} username={e.username} message={e.message}/>
))}
</React.Fragment>
)
}
}
Note: I've also passed a key to the CommentMessage component to give each child a stable identity (see docs for more info). Had to guess, but I assume a comment would have an id value, if not, you can choose a different unique value for the comment to use as a key.
Also I'd recommend moving to React Hooks over class-based components—a lot easier to work with once you get a grasp on hooks.

Data is not passing as a prop?

so as you can see below, when I hover over data it shows me the contents of data property (for the first two images) but as you see in the last image, I want to pass the data as a props but it's showing that data as an empty object. So why that's happening?
This is App.js
import React from 'react'
import {Cards,Chart,CountryPicker} from './components';
import styles from './App.module.css';
import { dataApi } from './api'
import image from './images/image.png'
// console.log(dataApi);
class App extends React.Component{
state = {
data : {},
country: '',
}
async componentDidMount () {
const dataFromApi = await dataApi();
this.setState({ data: dataFromApi})
// console.log(data);
// console.log('This is componentDidmount sec')
}
handleCountryChange = async(country) => {
const fetchedData = await dataApi(country);
this.setState({data: fetchedData, country: country})
}
render(){
const {data, country} = this.state
return(
<div className={styles.container}>
<img className={styles.image} src={image} alt="heading"/>
<Cards data = {data}/>
<CountryPicker handleCountryChange={this.handleCountryChange}/>
< Chart data={data} country={country}/>
</div>
);
}
}
export default App;
This Api.js
export const StateData = async(states) =>{
let stateName = 'Nagaland'
if(states){
stateName = states;
console.log("triggered")
}
try{
const response =await fetch(url);
const data = await response.json();
const pop = data.statewise;
const index = pop.findIndex(st => st.state === stateName)
const statedta = {
active :pop[index].active,
confirmed : pop[index].confirmed,
deaths : pop[index].deaths,
recovered: pop[index].recovered
}
return statedta
}catch(error){console.log(error)}
}
The default state value for your data field is an empty object.
This could be your IDE is just showing you the default value for that state field.
I would say that it's best to write a console.log("data:", data) on line 33,
after you destructure the values. Then induce the handleStateChange() method to execute. If the rest of the code is kosher, then you should see that console.log first show an empty object, and later show the expected data.

Dog API breeds return undefined 404 error React.js

I'm trying to get the specific photos that belong to a dog breed when a user clicks on a breed (for instance, they click on "hound" and then the photos of 3 random hounds appear). I continue to receive GET https://dog.ceo/api/breed/undefined/images/random/3 404 (404 error and returning undefined inside of the fetch url) in the dev tools when clicking on the specific breed. When I console.log breedName inside of getSelectedBreed, it comes back as the specific breed that the user clicked on however, once the interpreter gets to the method, selectBreed, the console.log returns undefined.
Here is my code for my App component and Breeds component, thank you!
App component:
import React, { Component } from 'react';
import './App.css';
import Header from '../Header/Header';
import Breeds from '../Breeds/Breeds';
class App extends Component {
constructor() {
super();
this.state = {
breedName: '',
breeds: [],
hasErrors : false
}
}
getSelectedBreed = (breedName) => {
this.setState({
breedName: breedName,
})
this.selectBreed();
}
selectBreed = (breedName) => {
console.log('breedName:', breedName)
fetch(`https://dog.ceo/api/breed/${breedName}/images/random/3`)
.then(res => res.json())
.then(breed => this.setState({ getSelectedBreed: breed.breedName }))
.catch(() => this.setState({ hasErrors: true }))
}
componentDidMount() {
const breedUrl = 'https://dog.ceo/api/breeds/list/all'
fetch(breedUrl)
.then(res => res.json())
.then(data => this.setState({breeds: data.message}))
.catch(() => this.setState({ hasErrors: true }))
}
render() {
console.log('breed', this.state.breedName)
return (
<div>
<Header />
<Breeds
breeds={this.state.breeds}
getSelectedBreed={this.getSelectedBreed}
/>
</div>
)
}
}
export default App;
and Breeds component:
import React, {Component} from 'react';
import './Breeds.css';
class Breeds extends Component {
constructor(props) {
super(props);
this.state={
}
}
render() {
const listOfBreeds = Object.keys(this.props.breeds)
const breedItems = listOfBreeds.map((breedName, index) =>
<div key={index}>
<div onClick={()=>this.props.getSelectedBreed(breedName)}>
<p className='breed-name' onClick={()=>this.props.getSelectedBreed(breedName)}>{breedName}</p>
</div>
</div>
)
return (
<div>
<div className='breed-container'>{breedItems}</div>
</div>
);
}
}
export default Breeds;
Looks like you forgot to pass bread name in selectBreed method.
getSelectedBreed = (breedName) => {
this.setState({
breedName: breedName,
})
this.selectBreed(breedName); //Missing breedName in your code
}

React Redux - passing arguments to event handler not working

Ok, I got a head scratcher I need a little bit of help with. The setup is that I have React/Redux app with a Categories page that reads a list of categories from an API, then lists them out. That part works fine. What I'm trying to do is pass in an event handler to each of the category child components that, when clicked, dispatches an action that toggles the state of the component, i.e., if the category is selected and clicked on, it will "unselect" it (which actually means deleting an entry from a database table called user_category), and if not selected, will "select" that category for that user (add an entry in the user_category table).
So I've got an onclick handler (handleCatClick) that is supposed to ultimately pass a categoryId and a userId to perform these operations. Unfortunately what I'm finding that even though these arguments are being passed to the function, they end up being undefined. So I'm not sure if I'm passing this function correctly or what exactly I've missed.
Everything works other than this - maybe you can help me spot the problem ;-)
Click here to view the database layout
Click here to see how the category page looks
The applicable pages in my app:
The architecture looks basically like this:
/views/[Categories]
- index.js (wrapper for the Categories Component)
- CategoriesComponent.jsx (should be self-explanatory)
[duck]
- index.js (just imports a couple of files & ties stuff together)
- operations.js (where my handleCatClick() method is)
- types.js (Redux constants)
- actions.js (Redux actions)
- reducers.js (Redux reducers)
[components]
[Category]
- index.jsx (the individual Category component)
/views/index.js(main Category page wrapper)
import { connect } from 'react-redux';
import CategoriesComponent from './CategoriesComponent';
import { categoriesOperations } from './duck'; // operations.js
const mapStateToProps = state => {
// current state properties passed down to LoginComponent (LoginComponent.js)
const { categoryArray } = state.categories;
return { categoryArray }
};
const mapDispatchToProps = (dispatch) => {
// all passed in from LoginOperations (operations.js)
const loadUserCategories = () => dispatch(categoriesOperations.loadUserCategories());
const handleCatClick = () => dispatch(categoriesOperations.handleCatClick());
return {
loadUserCategories,
handleCatClick
}
};
const CategoriesContainer = connect(mapStateToProps,mapDispatchToProps)(CategoriesComponent);
export default CategoriesContainer;
/views/CategoriesComponent.jsx (display layer for the Categories view)
import React from 'react';
import {Row,Col,Container, Form, Button} from 'react-bootstrap';
import {Link} from 'react-router-dom';
import './styles.scss';
import Category from './components/Category';
import shortid from 'shortid';
class CategoriesComponent extends React.Component {
constructor(props) {
super(props);
this.loadUserCats = this.props.loadUserCategories;
this.handleCatClick = this.props.handleCatClick;
}
componentWillMount() {
this.loadUserCats();
}
render() {
return (
<Container fluid className="categories nopadding">
<Row>
<Col xs={12}>
<div className="page-container">
<div className="title-container">
<h4>Pick your favorite categories to contine</h4>
</div>
<div className="content-container">
<div className="category-container">
{
this.props.categoryArray.map((item) => {
return <Category className="category" handleClick={this.props.handleCatClick} key={shortid.generate()} categoryData={item} />
})
}
</div>
</div>
</div>
</Col>
</Row>
</Container>
)
}
}
export default CategoriesComponent
/views/Categories/components/index.jsx (Single Category Component)
import React from 'react';
import {Row,Col,Container, Form, Button} from 'react-bootstrap';
import './styles.scss';
import Img from 'react-image';
class Category extends React.Component {
constructor(props) {
super(props);
this.state = {
categoryName: this.props.categoryData.category_name,
categoryImg: this.props.categoryData.category_img,
categoryId: this.props.categoryData.category_id,
userId: this.props.categoryData.user_id,
selected: this.props.categoryData.user_id !== null,
hoverState: ''
}
this.hover = this.hover.bind(this);
this.hoverOff = this.hoverOff.bind(this);
this.toggleCat = this.toggleCat.bind(this);
}
toggleCat() {
// the onClick handler that is supposed to
// pass categoryId and userId. When I do a
// console.log(categoryId, userId) these two values
// show up no problem...
const {categoryId, userId} = this.state;
this.props.handleClick(categoryId, userId);
}
hover() {
this.setState({
hoverState: 'hover-on'
});
}
hoverOff() {
this.setState({
hoverState: ''
});
}
render() {
const isSelected = (baseCat) => {
if(this.state.selected) {
return baseCat + " selected";
}
return baseCat;
}
return (
<div className={"category" + ' ' + this.state.hoverState} onClick={this.toggleCat} onMouseOver={this.hover} onMouseOut={this.hoverOff}>
<div className={this.state.selected ? "category-img selected" : "category-img"}>
<Img src={"/public/images/category/" + this.state.categoryImg} className="img-fluid" />
</div>
<div className="category-title">
<h5 className={this.state.selected ? "bg-primary" : "bg-secondary"}>{this.state.categoryName}</h5>
</div>
</div>
);
}
}
export default Category;
/views/Categories/duck/operations.js (where I tie it all together)
// operations.js
import fetch from 'cross-fetch';
import Actions from './actions';
import Config from '../../../../config';
const loadCategories = Actions.loadCats;
const selectCat = Actions.selectCat;
const unSelectCat = Actions.unSelectCat;
const localState = JSON.parse(localStorage.getItem('state'));
const userId = localState != null ? localState.userSession.userId : -1;
const loadUserCategories = () => {
return dispatch => {
return fetch(Config.API_ROOT + 'usercategories/' + userId)
.then(response => response.json())
.then(json => {
dispatch(loadCategories(json));
});
}
}
const handleCatClick = (categoryId, categoryUserId) => {
// HERE IS WHERE I'M HAVING A PROBLEM:
// for whatever reason, categoryId and categoryUserId
// are undefined here even though I'm passing in the
// values in the Category component (see 'toggleCat' method)
var params = {
method: categoryUserId !== null ? 'delete' : 'post',
headers: {'Content-Type':'application/json'},
body: JSON.stringify(
{
"category_id": categoryId,
user_id: categoryUserId !== null ? categoryUserId : userId
}
)
};
const toDispatch = categoryUserId !== null ? unSelectCat : selectCat;
return dispatch => {
return fetch(Config.API_ROOT + 'usercategories/', params)
.then(response => response.json())
.then(json => {
dispatch(toDispatch(json));
});
}
}
export default {
loadUserCategories,
handleCatClick
}
The problem that I am having:
So I'm thinking I'm either not referencing handleCatClick correctly, or I'm somehow not passing the categoryId and userId correctly so that when it finally gets to handleCatClick(categoryId, categoryUserId) in operations.js, it ends up as undefined. It's probably something simple but I can't spot it. NOTE: I haven't included files like the types.js or reducers.js, because they seem to be outside the scope of the problem, but if you need them please let me know. Thanks in advance for your help!
Try this changes: Add params to these handlers
const handleCatClick = (categoryId, categoryUserId) => dispatch(categoriesOperations.handleCatClick(categoryId, categoryUserId));
and
return <Category className="category" handleClick={(categoryId, categoryUserId) => this.props.handleCatClick(categoryId, categoryUserId)} key={shortid.generate()} categoryData={item} />

How to display data from Rails 5 API in React.js frontend app?

I have set up an API with Rails, with a http://localhost:3001/api/words endpoint exposing the following data:
[{"id":1,"term":"Reach","definition":"Reach is the number of people who had any content from your Page or about your Page enter their screen.","example":"","author":"Loomly","credit":"https://www.loomly.com/","created_at":"2018-11-02T03:21:20.718Z","updated_at":"2018-11-02T03:21:20.718Z"},{"id":2,"term":"Emoji","definition":"A small digital image or icon used to express an idea, emotion, etc., in electronic communication","example":"","author":"Loomly","credit":"https://www.loomly.com/","created_at":"2018-11-02T03:23:50.595Z","updated_at":"2018-11-02T03:23:50.595Z"}]
I am now trying to simply display this data (ideally as an unordered list) in a React.js frontend application built with Create React App, and here is the content of my App.js file:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor () {
super()
this.state = {}
this.getWords = this.getWords.bind(this)
this.getWord = this.getWord.bind(this)
}
componentDidMount () {
this.getWords()
}
fetch (endpoint) {
return window.fetch(endpoint)
.then(response => response.json())
.catch(error => console.log(error))
}
getWords () {
this.fetch('/api/words')
.then(words => {
if (words.length) {
this.setState({words: words})
this.getWord(words[0].id)
} else {
this.setState({words: []})
}
})
}
getWord (id) {
this.fetch(`/api/words/${id}`)
.then(word => this.setState({word: word}))
}
render () {
let {words, word} = this.state
return (
<div>
{Object.keys(words).map((key) => {
return (
<div key={word.id}>
<p>{word.term}</p>;
</div>
)
})}
</div>
)
}
}
export default App;
I believe the problem is located in the following area of the code:
render () {
let {words, word} = this.state
return (
<div>
{Object.keys(words).map((key) => {
return (
<div key={word.id}>
<p>{word.term}</p>;
</div>
)
})}
</div>
)
}
I have tried to follow the steps explained in this tutorial, as well as in that other tutorial, while keeping the layout of the page as simple as possible (no bells & whistles from semantic-ui-css), and no matter what I try, I keep getting into of the following errors:
TypeError: Cannot convert undefined or null to object
Unexpected token, expected “,”
Failed to compile: 'word' is not defined no-undef
The solution explained in this article led me to the code I have now, but there is something I am missing about the way to structure my React app: can you point me in the right direction?
getWords () {
fetch('http://localhost:3001/api/words')
.then((response) => {
return response.json();
})
.then((res) => {
// console.log(res); you should get the response you mentioned
this.setState({words: res});
});
}
Then check Are you getting data in your state by consoling it.
Then you can work on it using following
render{
return(
<div>
{ this.state.words.map((val) => (
<span>{val.term}</span>
))}
</div>
)
}
The problem is here: let {words, word} = this.state;
this.state doesnt have word property yet. You could initialize this.state like this:
this.state = {
words: [],
word: {}
};
be free to ask
There are two issues with the code in the question:
words & word are not defined.
Iteration through words in the render() function was not set properly with keys.
Thanks to the answers and comments left on this question, here is the code I ended up using:
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor () {
super()
this.state = {
words : [],
word : {}
}
this.getWords = this.getWords.bind(this)
this.getWord = this.getWord.bind(this)
}
componentDidMount () {
this.getWords()
}
fetch (endpoint) {
return window.fetch(endpoint)
.then(response => response.json())
.catch(error => console.log(error))
}
getWords () {
this.fetch('/api/words')
.then(words => {
if (words.length) {
this.setState({words: words})
this.getWord(words[0].id)
} else {
this.setState({words: []})
}
})
}
getWord (id) {
this.fetch(`/api/words/${id}`)
.then(word => this.setState({word: word}))
}
render () {
let {words} = this.state
return (
<ul className="words-container">
{Object.keys(words).map((key) => {
return (
<li className="word-container" key={key}>
{words[key].term}: {words[key].definition}.
</li>
)
})}
</ul>
)
}
}
export default App;

Categories