Console won't work / state doesnt update - javascript

i'm working on my code. First of all my App.js:
import React, { Component } from 'react';
// import logo from './logo.svg';
import './App.css';
import Flat from './components/flat.js'
class App extends Component {
constructor(props) {
super(props);
this.state = {
flats: []
};
}
componentDudMount() {
const url = "https://raw.githubusercontent.com/lewagon/flats-boilerplate/master/flats.json";
fetch(url)
.then(response => response.json())
.then((data) => {
this.setState({ //console.log(data);
flats: data
});
})
}
render() {
return (
<div className="app">
<div className="main">
<div className="search">
</div>
<div className="flats">
{this.state.flats.map((flat) => {
return <Flat flat={flat} />
})}
</div>
</div>
<div className="map">
</div>
</div>
);
}
}
export default App;
Then my flat.js
import React from "react";
import "./flat.css";
class Flat extends React.Component {
render() {
const title = this.props.flat.price
+ this.props.flat.priceCurrency + " - " + this.props.flat.name;
const style = {
backgroundImage: `url('${this.props.flat.imageUrl}')`
};
return (
<div className="flat">
<div className="flat-picture" style={style}></div>
<div className="flat-title">
{title}
</div>
</div>
);
}
}
export default Flat
First of all on line that should update state i wrote a console.log that should have give me a log in console. It doesnt, my console is blank. All i did was set up my react by create-react-app.
Second thing, my code doesnt fetch a json. I'm sitting on it for hours and can't see whats wrong. Thanks for help in advance.
Video that i'm following: https://www.youtube.com/watch?v=_ZTT9kw3PIE&t=3665s&index=4&list=PL3IsCNRIBp-jOC5vjf1ITYDVgwngDqtzz

You have a type in lifecyle hook componentDidMount
Secondly, a console.log() statement is not valid inside an object, so
this.setState({ //console.log(data);
flats: data
});
is invalid,
You can have a console.log() statement before setState like
fetch(url)
.then(response => response.json())
.then((data) => {
console.log(data);
this.setState({
flats: data
});
})
or use functional setState(although it isn't really useful here)
fetch(url)
.then(response => response.json())
.then((data) => {
this.setState(() => {
console.log(data);
return {flats: data}
});
})

it need to be componentDidMount() not componentDudMount() , one more thing
change your render methode to be like this , with this way you will only render when the state is updated
render() {
if(!this.state.flats){
return(<div>Loading ......</div>
}
return (
<div className="app">
<div className="main">
<div className="search">
</div>
<div className="flats">
{this.state.flats.map((flat) => {
return <Flat flat={flat} />
})}
</div>
</div>
<div className="map">
</div>
</div>
);
}

Related

Understanding async in React components

I'm using axios to return the details of a Pokemon:
class PokeDetails extends React.Component{
constructor(props){
super(props);
this.state = { pokemon: null }
}
componentDidMount(){
axios.get('https://pokeapi.co/api/v2/pokemon/1')
.then(res => {
this.setState({
pokemon: res.data
});
}).catch((err) => { console.log('Axios Poke Details error: ', err) });
}
render(){
const {pokemon} = this.state;
const pokeCard = pokemon ? (
<div className="poke-details">
<img src={pokemon.sprites.front_default} alt={`${pokemon.name} front`}/>
<h3 className="card-title">{pokemon.name}</h3>
</div>
) : (
<div className="center">Loading Pokemon...</div>)
return(
<div className="container">
{pokeCard}
</div>
)
}
}
export default PokeDetails
I want to display the pokemon types, which is an array that has a length of 1 or 2 depending on the pokemon. So I thought:
render(){
const {pokemon} = this.state
const listTypes = pokemon.types.map((type) => { <li>{type.name}</li> });
}
...and render listTypes in a ul in const pokeCard. When I do this I get an error saying pokemon is null. Shouldn't this not happen because of the ternary operator rendering the pokeCard?
Using pokemon before its detail get fetched from service, try like given:
const listTypes = pokemon && pokemon.types.map((type) => { <li>{type.name}</li> });
Problem is that componentDidMount is called after the initial render. So on the initial render, pokemon is null. That is why accessing pokemon.types fails.
You need to access pokemon.types only when pokemon is not null. Just move the code where you .map() over the pokemon.types inside the ternary operator.
const pokeCard = pokemon ? (
<div className="poke-details">
<img src={pokemon.sprites.front_default} alt={`${pokemon.name} front`} />
<h3 className="card-title">{pokemon.name}</h3>
<ul>
{pokemon.types.map(({ type }) => (
<li key={type.name}>{type.name}</li>
))}
</ul>
</div>
) : (
<div className="center">Loading Pokemon...</div>
);
Demo:
class PokeDetails extends React.Component {
constructor(props) {
super(props);
this.state = { pokemon: null };
}
componentDidMount() {
axios
.get("https://pokeapi.co/api/v2/pokemon/1")
.then((res) => {
this.setState({
pokemon: res.data
});
})
.catch((err) => {
console.log("Axios Poke Details error: ", err);
});
}
render() {
const { pokemon } = this.state;
const pokeCard = pokemon ? (
<div className="poke-details">
<img
src={pokemon.sprites.front_default}
alt={`${pokemon.name} front`}
/>
<h3 className="card-title">{pokemon.name}</h3>
<ul>
{pokemon.types.map(({ type }) => (
<li key={type.name}>{type.name}</li>
))}
</ul>
</div>
) : (
<div className="center">Loading Pokemon...</div>
);
return <div className="container">{pokeCard}</div>;
}
}
ReactDOM.render(<PokeDetails />,document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.20.0/axios.min.js"></script>
<div id="root"></div>
Rome data is better kept in cache, in case it gets shared between componenets in different levels.
I reproduced your code:
import React from 'react';
import useSWR from 'swr';
const fetcher = async (...args) => {
const res = await fetch(...args);
return res.json();
};
function PokeDetails() {
const { data, error } = useSWR('https://pokeapi.co/api/v2/pokemon/1', fetcher);
if (error) return <div>failed to load</div>;
if (!data) return <div>loading...</div>;
return (
<div>
{data ? (
data.map((pokemon) => {
return (
<div className='poke-details'>
<img
src={pokemon.sprites.front_default}
alt={`${pokemon.name} front`}
/>
<h3 className='card-title'>{pokemon.name}</h3>
</div>
);
})
) : (
<div>Some loading content</div>
)}
</div>
);
}
export default PokeDetails;
you can check this article to get a clear idea.

Undefined Props in React

I have the following component in react
import React, { Component } from 'react';
import axios from 'axios'
import Post from './Post'
import Navbar from '../Navbar'
class SinglePost extends Component {
constructor(props) {
super(props);
this.state = {
post: {}
}
}
componentDidMount() {
const { post_id } = this.props.match.params
axios.get(`http://127.0.0.1:8000/posts/${post_id}`)
.then(res => {
this.setState({ post: res.data })
console.log(res.data)
console.log(this.state)
})
.catch(err => console.log(err))
}
render() {
return (
<div>
<Navbar />
<div style={{height: '95px'}}></div>
<Post key={this.state.post.id} post={this.state.post} />
</div>
)
}
}
export default SinglePost;
The correct response is recieved from the backend and set to state properly. However the data passed to the Post component shows up as undefined.
Here is teh post component:
import React from 'react'
import Comment from './Comment'
import CommentForm from './CommentForm'
const placeholder_url = 'https://image.shutterstock.com/image-vector/ui-image-placeholder-wireframes-apps-260nw-1037719204.jpg'
const placeholder = 'data:image/png;base64,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'
function Post(props) {
console.log(props) //Shows up as an empty object
return (
<div className="uk-background-muted uk-margin-top uk-container">
<div>
<div className="uk-background-default">
<img src={props.post.user.pp || placeholder} width="50" height="50" alt={props.post.user.username} />
<a href={`/users/${props.post.user.id}`}>{props.post.user.username}</a>
</div>
<img className="uk-align-center" src={props.post.image || placeholder_url} alt="placeholder" />
<ul>
<li>Likes: {props.post.post_liked.length}</li>
<li>Dislikes: {props.post.post_disliked.length}</li>
<li>Comments: {props.post.comment_post.length}</li>
</ul>
<p className="uk-text-large">{props.post.text}</p>
{props.post.comment_post.map(comment => (<Comment key={comment.id} comment={comment} />))}
<div>
<CommentForm key={props.post.id} post={props.post} />
</div>
</div>
</div>
)
}
export default Post
This is the error I get: TypeError: Cannot read property 'pp' of undefined
State-setting is asynchronous, so whenever you want to act upon data from state, you should include a manual check against undefined values.
Try something like this:
class SinglePost extends Component {
constructor(props) {
super(props);
this.state = {
post: null // null initial value is easier to check than an empty object
}
}
componentDidMount() {
const { post_id } = this.props.match.params
axios.get(`http://127.0.0.1:8000/posts/${post_id}`)
.then(res => {
this.setState({ post: res.data })
console.log(res.data)
console.log(this.state)
})
.catch(err => console.log(err))
}
render() {
return (
<div>
<Navbar />
<div style={{height: '95px'}}></div>
{this.state.post &&
<Post key={this.state.post.id} post={this.state.post} />
}
</div>
)
}
}
The error happens in the first render when post = {} and the Post component is trying to get props.post.user.pp.
To solve the problem, in the SinglePost component, you could initialize post to be null instead of {} and in the Post component, handle the post === null case:
function Post(props) {
if(!props.post) {
return null;
// return <span>Loading</span>;
}
return (
<div className="uk-background-muted uk-margin-top uk-container">
...

Buttons won't render in React after mapping through array

I'm just learning React. I have two API calls to the backend MongoDB to pull names of vacations related to a user account. The names are saved to a JSON object and I'm trying to map through it to generate buttons on my React side but it's rendering nothing in that div. Everything else renders to the page. API calls are a mess because I thought that was the issue at first.
Profile Page
import React, { Component } from "react";
import Wrapper from "../components/Wrapper";
import { Container, Row, Col } from "../components/Grid";
import PastVacations from "../components/PastVacations";
import VacationBtn from "../components/VacationBtn"
import API from "../utils/API";
class Profile extends Component {
state = {
vacaIDs: [],
vacaNames: []
}
componentDidMount() {
this.getUser()
}
getUser = () => {
let IDsArr = []
API.getUser("Bill")
.then((res) => {
// console.log(res.data) Logs user found
res.data.vacations.forEach((VacaIDs) => {
let obj = {}
obj.name = VacaIDs;
IDsArr.push(obj)
// console.log(items) Logs Vacation IDs
})
console.log(IDsArr)
this.setState({
vacaIDs: IDsArr
})
this.getNames()
}).catch((err) => {
console.log(err)
})
}
getNames = () => {
let namesArr = []
this.state.vacaIDs.forEach((names) => {
console.log(names.name)// Logs vacation IDs
let obj = {}
API.getVacations(names.name).then((res) => {
console.log(res.data.name)// Logs Vacation names
obj.name = res.data.name;
namesArr.push(obj)
}).catch((err) => {
console.log(err.response)
})
})
this.setState({
vacaNames: namesArr
})
}
render() {
return (
<div className="">
<div className="row justify-content-around">
<div className="col-md-6">
{this.state.vacaNames.map(items => (
<VacationBtn
name={items.name}
/>
))}
</div>
<div className="col-md-4">
<div className="card">
<h5 className="card-header">
Card title
</h5>
<div className="card-body">
<p className="card-text">
Card content
</p>
</div>
<div className="card-footer">
Card footer
</div>
</div>
</div>
</div>
</div>
);
}
}
export default Profile;
VacationBtn Component
import React, { Component } from "react";
import "./style.css";
class VacationBtn extends Component {
render() {
return (
<button type="button" className="btn btn-primary">{this.props.name}</button>
);
}
}
export default VacationBtn;
Use Promise.all
Your current code is iterating for API calls but setState happens before any of the api calls are resolved.
getNames = () => {
let namesArr = [];
const promises = [];
this.state.vacaIDs.forEach((names) => {
promises.push(API.getVacations(names.name));
});
Promise.all(promises).then((values) => {
// do data manipulation here
values.forEach((val) => {
namesArr.push(val.data.name);
});
this.setState({
vacaNames: namesArr,
});
});
};
As #chandan_kr_jha noticed you're updating state before API is finished it's work.
A bit fancier code below with the same idea behind:
getNames = async () => {
const promises = this.state.vacaIDs.map((names) => API.getVacations(names.name));
const vacations = await Promise.all(promises);
this.setState({
vacaNames: vacations.map(v => v.data.name),
});
};

React "map" is undefined/empy while there is a state array?

I have a weird problem when I console log my component on load to check if there is a state. I Get an array back with data. But when I try to loop through it. I get map undefined? I don't understand why it's driving me crazy.
What am i doing wrong? I used the same thing on other components without any problems.
Thanks!
My code:
import React, { Component } from 'react';
import ReactHtmlParser from 'react-html-parser';
// API settings
import { WP_DATA_URL } from 'constants/import';
// Axios fetching
import axios from 'axios';
// components
import Youtube from 'components/Youtube/Youtube';
import Slider from 'react-slick';
import SpinnerLoader from 'components/SpinnerLoader/SpinnerLoader';
class College extends Component {
state = {
page_college: [],
loading: true,
};
getCoffee() {
return new Promise(resolve => {
setTimeout(() => resolve('☕'), 1000); // it takes half of a second to make coffee
});
}
async showData() {
try {
const wpCollege = axios(`${WP_DATA_URL}/pages?slug=college`);
await this.getCoffee();
await Promise.all([wpCollege]).then(response => {
this.setState({
page_college: response[0].data[0].acf,
loading: false,
});
console.log(this.state.page_college);
});
} catch (e) {
console.error(e); // 💩
}
}
componentDidMount() {
this.showData();
}
render() {
const { loading } = this.state;
const { title, description, page_college: college } = this.state;
return (
<div className="pages--container">
<div className="pages">
<div className="row center-xs pages--wrapper">
<div className="page">
<div className="page--content">
{loading ? (
<SpinnerLoader />
) : (
<React.Fragment>
<div className="col-xs-12 col-md-5">
<h2>HOI</h2>
</div>
<div className="col-xs-12 col-md-6">
{college.map(data => {
console.log(data);
})}
</div>
</React.Fragment>
)}
</div>
</div>
</div>
</div>
</div>
);
}
}
export default College;
setState is asynchronous so your console.log after it may be reflecting the previous state. Pass setState a callback as the 2nd param and check the state there. response[0].data[0].acf might not be an array.
async componentDidMount() {
await this.showData();
}
Just make the componentDidMount wait for the showData to complete.

Passing more than one props to one component

How can I pass more than one props to one component?
For example, I want to render a DIV that has various elements but not all the elements are in the same component.
So here I'm passing the whole component into another component, the problem with this is that ContentSkuInfo component is mapping throw states so all the data is loading in the same place, I need to load de data but ones in each DIV.
I need this:
<div>
<strong>Data 01</strong>
</div>
<div>
<strong>Data 02</strong>
</div>
<div>
<strong>Data 03</strong>
</div>
But I'm having this:
<div>
<strong>Data 01</strong>
<strong>Data 02</strong>
<strong>Data 03</strong>
</div>
This are my components Seccion_uno_contenido.js
import React from 'react';
import ContentSkuInfo from './CallDataStorage';
const ContenidoUno = (props) => {
return (
<div className="contentBox outerBox-first" >
<a href={props.link}>
<img src={props.imagen} alt="bloque 01" className="img-wrap" />
</a>
<h3>{props.categoria}</h3>
<img src={props.icono} className="iconic" alt="producto" />
<span>{props.descripcion}</span>
<ContentSkuInfo />
<small>Antes: ${props.antes}</small>
<div className="containerBotonRow">
¡Lo quiero!</button>
</div>
</div>
);
}
export default ContenidoUno;
CallDataStorage.js
import React from 'react';
import axios from 'axios';
var productsIds = ['3552357', '2635968BC', '3181464', '3593754'];
var productsIdsJoin = productsIds.join('-');
const getProductDetailAPI = (productsIds, storeId) => ({
method: 'GET',
baseURL:`SORRY CAN'T SHOW THIS`,
auth: {
username: 'XXXXXXX',
password: 'XXXXXXX',
},
headers: {
'Content-Type': 'application/json',
},
data: {},
});
const ContentSkuInfo = (props) => {
return (
<strong>$ {props.prodsNormal}</strong>
);
}
class DataStorage extends React.Component {
constructor(props) {
super(props);
this.state = { products: [] };
};
getWebServiceResponse = (currentList, storeId) => (
axios(getProductDetailAPI(currentList, storeId))
.then(response => response.data)
.then(newData => {
this.setState({ products: newData.productDetailsJson })
})
.catch(e => e)
);
componentDidMount() {
this.getWebServiceResponse(productsIdsJoin, 96);
};
render() {
return (
<samp>
{this.state.products.map(skuData =>
<ContentSkuInfo
prodsName={skuData.name}
prodsId={skuData.productId}
prodsStatus={skuData.status}
prodsPublished={skuData.published}
prodsNormal={skuData.NORMAL}
prodsCMR={skuData.CMR}
prodsAhorro={skuData.savings}
prodsCombo={skuData.combo}
prodsStock={skuData.stockLevel}
/>
)
}
</samp>
)
}
}
export default DataStorage;
Just change the definition of ContentSkuInfo in CallDataStorage.js to
const ContentSkuInfo = (props) => (
<div>
<strong>$ {props.prodsNormal}</strong>
</div>
)
You are not passing any props to ContentSkuInfo component in your code. To access something via props inContentSkuInfo you need to pass some properties to ContentSkuInfo.
For eg
When you are calling ContentSkuInfo In your ContenidoUno component you need to send something like below
<ContentSkuInfo prodsNormal=“testing” prodsNormal2=“test2”/>
And in ContentSkuInfo component you can get it like remove $ from expression
Without return
const ContentSkuInfo = (props) => (
<div>
<div>
<strong>{props.prodsNormal}</strong>
</div>
<div>
<strong>{props.prodsNormal2}</strong>
</div>
</div>
)
With return
const ContentSkuInfo = (props) => {
return (
<div>
<div>
<strong>{props.prodsNormal}</strong>
</div>
<div>
<strong>{props.prodsNormal2}</strong>
</div>
</div>
)}

Categories

Resources