Send Array from API Response to Components - reactjs

I'm trying to capture wepb url from an axios response and pass it to an image component.
I want to loop through data and show every data[all].images.original.webp
I've tried .map() with no success
I think some of my problems involve waiting on the response to finish, and UserItem is probably all wrong
Here is the console.log I get during troubleshooting.
App
import React, { Component } from "react";
import axios from "axios";
import Users from "./components/Users";
class App extends Component {
state = {
users: [] /* Set users inital state to null */,
loading: false
};
async componentDidMount() {
this.setState({ loading: true });
const res = await axios.get(
"http://api.giphy.com/v1/stickers/search?q=monster&api_key=sIycZNSdH7EiFZYhtXEYRLbCcVmUxm1O"
);
/* Trigger re-render. The users data will now be present in
component state and accessible for use/rendering */
this.setState({ users: res.data, loading: false });
}
render() {
return (
<div className="App">
<Users loading={this.state.loading} users={this.state.users} />
{console.log(this.state.users)}
</div>
</div>
);
}
}
export default App;
Users Component
import React, { Component } from "react";
import UserItem from "./UserItem";
export default class Users extends Component {
render() {
return (
<div className="ui relaxed three column grid">
{this.props.users.map(data => (
<UserItem key={data.id} gif={data.images.original.webp} />
))}
</div>
);
}
}
UserItem
import React from "react";
import PropTypes from "prop-types";
const UserItem = ({ user: { gif } }) => {
return (
<div className="column">
<img src={gif} className="ui image" />
</div>
);
};
UserItem.propTypes = {
user: PropTypes.object.isRequired
};
export default UserItem;
Error Message

So it took me a while to read up on the giphy api, but it turns out you might possibly be using the wrong protocol, http instead of https, so the axios call was actually throwing an error and that was getting saved in state since your code doesn't handle it, i.e. state.users wasn't an array to map over.
axios.get("https://api.giphy.com/v1/stickers/search?q=monster&api_key=sIycZNSdH7EiFZYhtXEYRLbCcVmUxm1O")
The response data is also response.data.data, and your UserItem component just receives gif as a prop, not the user object. I've coded up a working sandbox.

Render the output in a conditional expression so that it does not try to render the .map() before the array of data is available. Likely that this.props.users is undefined the first time the component tries to render and so you get a fatal TypeError.
{this.props.users && this.props.users.map(data => (
<UserItem key={data.id} gif={data.images.original.webp} />
))}
The && expression acts as a boolean conditional when used like this. Now the first time the component tries to render and this.props.users is undefined it will not try and run the .map(), when the props update and the array is available it will render.

Related

TypeError: robots.map is not a function

I keep getting this error: TypeError: robots.map is not a function.
I reviewed the code several times can't find the bug.
import React from 'react';
import Card from './Card';
// import { robots } from './robots';
const CardList = ({ robots }) => {
return(
<div>
{
robots.map((user, i) => {
return (
<Card
key={i}
id={robots[i].id}
name={robots[i].name}
email={robots[i].email}
/>
);
})
}
</div>
);
}
export default CardList;
App.js
import React, { Component } from 'react';
import CardList from './CardList';
import SearchBox from './SearchBox';
import { robots } from './robots';
class App extends Component {
constructor(){
super()
this.state = {
robots:'robots',
searchfield: ''}
}
render(){
return(
<div className='tc'>
<h1 className=''>RoboFriends</h1>
<SearchBox />
<CardList robots={this.state.robots}/>
</div>
);
}
}
export default App;
I updated the initial code with App.js that calls CardList.
I recently started learning react and I hope to develop an app that lets you search for a user which instantly filters and render the name typed in the search box.
You pass robots as props from App internal state and not from the imported file.
Set the state of App component from the imported robots file
import { robots } from './robots'
class App extends Component {
constructor() {
super()
this.state = {
robots,
searchfield: ''
}
}
render() {
return (
<div className='tc'>
<h1 className=''>RoboFriends</h1>
<SearchBox />
<CardList robots={this.state.robots}/>
</div>
);
}
}
Also using index as React key is a bad practice, You have a unique id in every robot object so use it as key, also read about the map function and how to access the iterated elements
const CardList = ({ robots }) => (
<div>
{robots.map(robot => (
<Card
key={robot.id}
id={robot.id}
name={robot.name}
email={robot.email}
/>
))}
</div>
);
You're passing a string to be mapped, instead pass the robots list of objects and see the result.
These kind of errors are the result of passing something other than a list to be mapped

TypeError: Cannot read property 'map' of undefined Reactjs

Newbie to React and I need help again - everything was fine untill i shifted the code from App.js to separate component - and b/c it is the stateless component, i am using props and map function to access the value and state from App.js but it is not happy -- help please
import React from 'react';
const Recipes = props => (
<div>
{props.recipes.map(recipe => (
<div key={recipe.recipe_id}>
<img src={recipe.image_url} alt={recipe.title} />
<p>{recipe.title}</p>
</div>
))}
</div>
);
export default Recipes;
This just means that you don't pass in recipes properly as a prop where you render <Recipes />. Either recipes is null or incorrectly formatted.
ex:
// App.js
import React from 'react';
const App = () => {
const recipes = [{
recipe_id: '<id>',
image_url: '<some url>',
title: 'Lé title'
}];
// recipes could be null/undefined or not even passed as a prop
return (
<Recipes recipes={recipes} />
);
}
export default App;
So it's hard to know exactly what's happening without being able to see how you are passing down props, and exactly what data they contain. The error you are getting implies that you aren't actually sending the recipes array correctly.
I honestly never use stateless functions in react anymore, because PureComponent generally preforms better because of it's built in shouldComponentUpdate which prevents unnecessary re-renders. So here's how I would write that component:
import React, { PureComponent } from 'react';
class Recipes extends PureComonent {
recipeList = () => {
const recipes = this.props;
const recipeArray = recipes.map((recipe) => {
<div key={recipe.recipe_id}>
<img src={recipe.image_url} alt={recipe.title} />
<p>{recipe.title}</p>
</div>
});
return recipeArray;
}
render () {
return () {
<div>
{this.recipeList()}
</div>
}
}
}
export default Recipes;
That being said, my guess about the way you wrote your component is that if you were to console out props you would find that it was actually equal to recipes, which is why recipes.recipes is undefined.

Reactjs: Axios.post call returns array object from database - need to map to other component

I've been struggling with this for a couple days, and any help would be appreciated.
In this component, I have tried to do an HTTP call to my server and database. After parsing the response, using JSON.parse, I am getting back a correctly formed JSON object. I then want to map through that object and for each return a new component (called HistoryItem).
The code below attempts to do this by placing the object into the component state, but it is causing an infinite refresh loop. Previously I had tried a functional component.
The original iteration of this component did work. But it pulled a static JSON object from my client side files. Therefore, I am confident code works without the http call.
It seems to me I am doing something wrong with the async, which is disallowing the JSON object received asynchronously from being rendered.
Below is the main component. Note the component imports the username from redux. This feeds the HTTP call, so that it retrieves only records associated with the logged in user. Again, everything looks fine on the server/database end...
import React, {Component} from 'react';
import style from './history.css';
import HistoryItem from './HistoryItem/historyItem';
import data from '../../config/fakermyhistory.json';
import {Link} from 'react-router-dom';
import {connect} from 'react-redux';
import axios from 'axios';
class History extends Component {
constructor(props) {
super(props);
this.state = {
compiledList:[]
}
}
getData(){
this.state.compiledList.map((call, i) => {
const shaded = (call.rated) ? 'lightgrey' : 'white';
console.log("shaded", shaded);
return(
<Link to={`/reviewpage/${call._id}`} key={call._id}
style={{ textDecoration: 'none', color:'lightgrey'}}>
<div style={{backgroundColor:shaded}}>
<hr/>
<HistoryItem call={call}/>
</div>
</Link>
)
})
}
render(){
axios.post('/api/history', {username: this.props.username})
.then((res) => {
const array = JSON.parse(res.request.response);
this.setState({compiledList: array})
console.log("res", array);}
).catch((err) => console.log("err", err));
return (
<div className={style.container}>
<div className={style.historyHeader}>
<div className={style.historyHeaderText}>
Your Call History
</div>
</div>
<div className={style.historyList}>
{this.getData()};
</div>
</div>
)
}
}
const mapStateToProps = state => {
return {
username:state.auth.username
};
}
export default connect(mapStateToProps, null)(History);
Thanks in advance if you can help.
Here is another version using it as a functional component. Also doesn't render (although no errors on this one)
import React, {Component} from 'react';
import style from './history.css';
import HistoryItem from './HistoryItem/historyItem';
import data from '../../config/fakermyhistory.json';
import {Link} from 'react-router-dom';
import {connect} from 'react-redux';
import axios from 'axios';
const History =(props)=> {
const getData=(props)=>{
console.log("props", props);
axios.post('/api/history', {username: props.username})
.then((res) => {
const array = JSON.parse(res.request.response);
console.log("array", array);
array.map((call, i) => {
const shaded = (call.rated) ? 'lightgrey' : 'white';
console.log("shaded", shaded);
return(
<Link to={`/reviewpage/${call._id}`} key={call._id}
style={{ textDecoration: 'none', color:'lightgrey'}}>
<div style={{backgroundColor:shaded}}>
<hr/>
<HistoryItem call={call}/>
</div>
</Link>
)
})
}
).catch((err) => console.log("err", err));
}
return (
<div className={style.container}>
<div className={style.historyHeader}>
<div className={style.historyHeaderText}>
Your Call History
</div>
</div>
<div className={style.historyList}>
{getData(props)};
</div>
</div>
)
}
const mapStateToProps = state => {
return {
username:state.auth.username
};
}
export default connect(mapStateToProps, null)(History);
Instead of calling axios in render function, try to invoke it from componentDidMount.
This will help you prevent the infinite loop.
To return the components rendered within the map function, it was necessary to add a "return" command before the map function was called:
return array.map((call, i) => {...

ReactJS - JSON objects in arrays

I am having a little problem and can't seem to understand how to fix it. So I am trying to create a pokemon app using pokeapi. The first problem is that I can't get my desired objects to display. For example I want to display {pokemon.abilities[0].ability}, but it always shows Cannot read property '0' of undefined but just {pokemon.name} or {pokemon.weight} seems to work. So the problem appears when there is more than 1 object in an array.
import React, {Component} from 'react';
export default class PokemonDetail extends Component{
constructor(props){
super(props);
this.state = {
pokemon: [],
};
}
componentWillMount(){
const { match: { params } } = this.props;
fetch(`https://pokeapi.co/api/v2/pokemon/${params.id}/`)
.then(res=>res.json())
.then(pokemon=>{
this.setState({
pokemon
});
});
}
render(){
console.log(this.state.pokemon.abilities[0]);
const { match: { params } } = this.props;
const {pokemon} = this.state;
return (
<div>
{pokemon.abilities[0].ability}
<img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${params.id}.png`} />
</div>
);
}
}
And also some time ago I added the router to my app, so I could pass id to other components, but the thing is I want to display pokemonlist and pokemondetail in a single page, and when you click pokemon in list it fetches the info from pokeapi and display it in pokemondetail component. Hope it makes sense.
import React, { Component } from 'react';
import { BrowserRouter as Router, Route} from 'react-router-dom';
import './styles/App.css';
import PokemonList from './PokemonList';
import PokemonDetail from './PokemonDetail';
export default class App extends Component{
render(){
return <div className="App">
<Router>
<div>
<Route exact path="/" component={PokemonList}/>
<Route path="/details/:id" render={(props) => (<PokemonDetail {...props} />)}/>
</div>
</Router>
</div>;
}
}
In case componentWillMount(), An asynchronous call to fetch data will not return before the render happens. This means the component will render with empty data at least once.
To handle this we need to set initial state which you have done in constructor but it's not correct. you need to provide default values for the abilities which is an empty array.
So change it to
this.state = {
pokemon: {
abilities: []
}
}
And then inside render method before rendering you need to verify that it's not empty
render() {
return (
(this.state.pokemon.abilities[0]) ?
<div>
{console.log(this.state.pokemon.abilities[0].ability)}
<img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png`} />
</div> :
null
);
}
It is common in React that you always need to safe-check for existence of data before rendering, especially when dealing with API data. At the time your component is rendered, the state is still empty. Thus this.state.pokemon.abilities is undefined, which leads to the error. this.state.pokemon.name and this.state.pokemon.weight manage to escape same fate because you expect them to be string and number, and don't dig in further. If you log them along with abilities, they will be both undefined at first.
I believe you think the component will wait for data coming from componentWillMount before being rendered, but sadly that's not the case. The component will not wait for the API response, so what you should do is avoid accessing this.state.pokemon before the data is ready
render(){
const { match: { params } } = this.props;
const {pokemon} = this.state;
return (
<div>
{!!pokemon.abilities && pokemon.abilities[0].ability}
<img src={`https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${params.id}.png`} />
</div>
);
}

React doesn't change to LoggedInView when Meteor user logs in

I am developing a React + Meteor application and I'm having trouble with the user login functionality.
I have a header navbar that displays a different component based on whether or not the user is logged in.
Like this:
export default class Header extends Component {
constructor(props) {
super(props)
this.state = {
user: Meteor.user()
}
}
render() {
return (
<header className="main-header">
<nav className="navbar navbar-static-top">
<div className="navbar-custom-menu">
{this.state.user() !== null ? <LoggedInNavigation /> : <LoggedOutNavigation />}
</div>
</nav>
</header>
)
}
}
Now this works but it doesn't change upon a user being logged in. I have to refresh the page in order to change the views (which obviously is not ideal).
Here is my login code:
Meteor.loginWithPassword(this.state.email, this.state.password, (error) => {
if (error)
this.setState({ meteorError: "Error: " + error.reason })
else {
this.setState({ meteorError: "" })
// Handle successful login
}
})
The problem is these two blocks of code sit in different components.
The first block is in imports/ui/components/main-layout/Header and the second block is in imports/ui/components/authentication/Login.
As I said, the problem is that the user can log in but the view doesn't change according to the authentication state. What's the best practice to solving this?
EDIT:
Here is the hierarchy of components:
1 - LoggedOutNav
MainLayout -> Header -> LoggedOutNav
2 - Login Code
MainLayout -> Routes -> (Route path="/login" component={Login}) -> LoginForm
The problem here is that the constructor of your class will only run once and never again as long as the component is mounted. So even though Meteor.user() will change, your state won't. The component will rerender when a) the props change or b) your state changes e.g. when you call setState. We can leverage a) through meteors createContainer HOC (react-meteor-data) to wrap your Header class and set a reactive data context for it. When the data changes, the props for Header will change and the component rerenders. In code that would be something like:
import { Meteor } from 'meteor/meteor';
import { createContainer } from 'meteor/react-meteor-data';
import React, { Component, PropTypes } from 'react';
class HeaderComponent extends Component {
render() {
const { user } = this.props;
return (
<header className="main-header">
<nav className="navbar navbar-static-top">
<div className="navbar-custom-menu">
{user ? <LoggedInNavigation /> : <LoggedOutNavigation />}
</div>
</nav>
</header>
)
}
}
export const Header = createContainer(() => {
// assuming you have a user publication of that name...
Meteor.subscribe('users/personalData');
return {
user: Meteor.user(),
};
}, HeaderComponent);

Resources