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.
Related
I will appreciate your help with my project.
I created button filters for a component that displays list of courses in a nextjs project. When I click on a button it filters the courses but when I refresh the page it gives me the error below:
./pages/index.js
Module parse failed: Identifier 'courses1' has already been declared (15:15)
File was processed with these loaders:
./node_modules/next/dist/build/webpack/loaders/next-swc-loader.js
You may need an additional loader to handle the result of these loaders.
Please see code below.
import { useState, useEffect } from "react"
import axios from "axios"
import CourseCard from "../components/cards/CourseCard";
import { Button } from 'antd'
const Index = ({ courses }) => {
const allTopics = ['All', ...new Set(courses.map(courses => courses.topic))]
const [courses, setCourses] = useState(courses)
const [buttons, setButtons] = useState(allTopics)
const filter = (button) => {
if (button === 'All') {
setCourses(courses)
}
const filteredData = courses.filter(courses => courses.topic === button)
setCourses(filteredData)
}
return (
<>
<h1 className="jumbotron p-5 text-center bg-primary text-white square">OEP</h1>
<div className="container-fluid">
<div>
{
buttons.map((topic, i) => {
return <Button onClick={() => filter(topic)} className="btn ms-2">{topic}</Button>
})
}
</div>
<div className="row">
{courses.map((course) => <div key={course._id} className="col-md-4">
<CourseCard course={course} />
</div>)}
</div>
</div>
</>
)
};
export async function getServerSideProps() {
const { data } = await axios.get(`${process.env.API}/courses`);
return {
props: {
courses: data,
},
}
}
export default Index;
The constant courses you declared (in line 15) already existed. It was destructured from the parameters of Index. Try to change the name of one of these 2 variables.
I have React Components:
My account.jsx
import "./myAccount.sass";
import { auth } from "../../firebaseConfig";
import {
signOut,
onAuthStateChanged,
} from "https://www.gstatic.com/firebasejs/9.6.0/firebase-auth.js";
import { useEffect, useState } from "react";
import { Hotel } from "../Hotel/Hotel";
function MyAccount(props) {
const { isauth } = props;
const { hotels = [] } = props;
const [userEmail, setUserEmail] = useState([]);
function logout() {
signOut(auth);
isauth(false);
console.log("Successful logout");
}
function userInfo() {
onAuthStateChanged(auth, (user) => {
console.log(`user info: ${user.email}"`);
setUserEmail(user.email);
});
}
useEffect(() => {
userInfo();
}, []);
return (
<div>
<h3>Hi, {userEmail}</h3>
<button onClick={logout}>Logout...</button>
<h4>Your reviews:</h4>
<hr />
<div className="Hotels">
{hotels.map((hotel, id) => (
<Hotel key={id} {...hotel} filter={true} user={userEmail} />
))}
</div>
</div>
);
}
export default MyAccount;
Hotels.jsx
import starsMap from "../../starsMap";
import { useEffect, useState } from "react";
import "./hotel.sass";
function Hotel(props, filter = false, user = null) {
const { name, img, localization, stars, review, author } = props;
const [userEmail, setUserEmail] = useState(user);
if (filter) {
console.log(author, userEmail);
if (author === user) {
return (
<div className="Hotel_card">
<h2 className="Hotel_card-name">{name}</h2>
<div className="Hotel_card_wrapper">
<img className="Hotel_card-img" src={img} alt="hotel_img" />
<h3 className="Hotel_card-localization">
{/* Lat:{localization._lat}
Long:{localization._lat} */}
<button>Show on map</button>
</h3>
{starsMap.get(stars)}
</div>
<p className="Hotel_card-review">{review}</p>
<h5 className="Hotel_card-author">Wroten by {author}</h5>
</div>
);
}
} else {
return (
<div className="Hotel_card">
<h2 className="Hotel_card-name">{name}</h2>
<div className="Hotel_card_wrapper">
<img className="Hotel_card-img" src={img} alt="hotel_img" />
<h3 className="Hotel_card-localization">
{/* Lat:{localization._lat}
Long:{localization._lat} */}
<button>Show on map</button>
</h3>
{starsMap.get(stars)}
</div>
<p className="Hotel_card-review">{review}</p>
<h5 className="Hotel_card-author">Wroten by {author}</h5>
</div>
);
}
}
export { Hotel };
The MyAccount.jsx component passes two attributes to the Hotels.jsx component - filter={true} user={userEmail}.
Hotels.jsx only sees filter={true}, user remains null for it.
Question - why doesn't Hotels.jsx see the user={userEmail} passed to it?
If you look at the state userEmail in MyAccount.jsx, then the desired value is found this way, but it is lost at the moment it is transferred to Hotels.jsx
It's because you are using an intermediate useState in the Hotel.jsx file and you are not receiving correctly the props
const [userEmail, setUserEmail] = useState(user);
remove that line (it seems that you are not using it anyway) and change in the Hotels.jsx component the prop user to userEmail:
function Hotel({filter = false, userEmail = null, ...props})
A React Functional Component, is a function that returns some JSX code. When you write it like:
<Hotel key={id} {...hotel} filter={true} user={userEmail} />
This is JSX, and this code gets converted to React first class API:
React.createElement(
Hotel,
[props],
[...children]
)
props are filter, user and whatever comes out of {...hotel}.
The React.createElement just takes props and children ( if at all ), and passes an object to your Hotel function with them all inside, remember that props is an object:
Hotel(props)
Being an object, when you want to recall your props from within your Component, you have two possible ways:
function Hotel (props) {
console.log(props.filter, props.user)
}
or through destructuring them directly when declaring them:
function Hotel ({filter, user}) {
console.log(filter, user)
}
If you are passing other props that you don't want to name directly:
function Hotel ({filter, user, ...props}) {
console.log(filter, user, props)
}
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),
});
};
This is the first time I came across handling the promises inside the JSX in my React JS project.
Here is my component code.
import React from 'react';
import Sodexo from './Sodexo';
import { connect } from 'react-redux';
import {withCookies} from 'react-cookie';
import ticketImg from './../../images/web-images/ticketrest.png';
import sodexImg from './../../images/web-images/sodexo.png';
import {selectMealVoucher} from './../../actions/paymentActions';
import {getSavedCard} from './../../utils/PaymentGateway';
class MealVoucher extends React.Component {
checkCardSaved = async () => {
const {cookies} = this.props.cookies;
const card = await getSavedCard(cookies.id,cookies.token);
const {sodexo} = card.data;
return sodexo.length === 0 ? 0 : 1;
}
render() {
const {sodexo, ticketrestaurant} = this.props;
return (
<div>
<div class="row">
<div className="col-md-1 col-sm-1"></div>
<div class="col-md-5 col-sm-5">
<div class="form-group">
<input
type="radio"
name="mealvoucher"
{...sodexo.isActive ? {checked: true} : {}}
onChange={() => this.props.selectMealVoucher('sodexo')}
/>
<img src={sodexImg} height="30px" style={{marginLeft:'15px'}}/>
</div>
</div>
<div class="col-md-5 col-sm-5">
<div class="form-group">
<input
type="radio"
name="mealvoucher"
{...ticketrestaurant ? {checked: true} : {}}
onChange={() => this.props.selectMealVoucher('ticketrestaurant')}
/>
<img src={ticketImg} height="30px" style={{marginLeft:'15px'}} />
</div>
</div>
</div>
{
sodexo.isActive ? (
this.checkCardSaved().then(res => {
res ? <Sodexo /> : ''
})
): ''
}
</div>
);
}
}
const mapStateToProps = state => state.paymentpage.paymentoption.mealvouchers;
const mapDispatchToProps = {selectMealVoucher};
export default withCookies(connect(mapStateToProps,mapDispatchToProps)(MealVoucher));
In the above, I am trying to call checkSavedCard() inside the JSX, but even if I am returning the 0 or 1 from checkSavedCard(), I see that promise is getting returned instead of 0 or 1.
So I used .then() and tried to render another component depending on the value returned by the checkSavedCard().
But, this isn't working and instead, I am getting an error message.
Objects are not valid as a React child (found: [object Promise]).
So, I came up with a different approach.
I created one global variable and inside the checkSavedCard() instead of returning the value I am saving that value to the global variable and then inside the JSX I am checking for the value of that global variable.
This approach works fine for me.
Here is the working component code.
import React from 'react';
import Sodexo from './Sodexo';
import { connect } from 'react-redux';
import {withCookies} from 'react-cookie';
import ticketImg from './../../images/web-images/ticketrest.png';
import sodexImg from './../../images/web-images/sodexo.png';
import {selectMealVoucher} from './../../actions/paymentActions';
import {getSavedCard} from './../../utils/PaymentGateway';
class MealVoucher extends React.Component {
cardStatus;
componentDidMount() {
this.checkCardSaved();
}
checkCardSaved = async () => {
const {cookies} = this.props.cookies;
const card = await getSavedCard(cookies.id,cookies.token);
const {sodexo} = card.data;
this.cardStatus = sodexo.length === 0 ? 0 : 1;
}
render() {
const {sodexo, ticketrestaurant} = this.props;
return (
<div>
<div class="row">
<div className="col-md-1 col-sm-1"></div>
<div class="col-md-5 col-sm-5">
<div class="form-group">
<input
type="radio"
name="mealvoucher"
{...sodexo.isActive ? {checked: true} : {}}
onChange={() => this.props.selectMealVoucher('sodexo')}
/>
<img src={sodexImg} height="30px" style={{marginLeft:'15px'}}/>
</div>
</div>
<div class="col-md-5 col-sm-5">
<div class="form-group">
<input
type="radio"
name="mealvoucher"
{...ticketrestaurant ? {checked: true} : {}}
onChange={() => this.props.selectMealVoucher('ticketrestaurant')}
/>
<img src={ticketImg} height="30px" style={{marginLeft:'15px'}} />
</div>
</div>
</div>
{
sodexo.isActive && this.cardStatus ? (
<Sodexo />
): ''
}
</div>
);
}
}
const mapStateToProps = state => state.paymentpage.paymentoption.mealvouchers;
const mapDispatchToProps = {selectMealVoucher};
export default withCookies(connect(mapStateToProps,mapDispatchToProps)(MealVoucher));
But I think this isn't a perfect solution, there might be something provided by React JS, to handle the promises inside the JSX.
I googled it but I didn't find any solution on this.
React can't render from the result of a Promise. You should update a value in the component's state and render based on the state's value. See my example here: https://codesandbox.io/s/1vzon8r4k4. A button click sets the state to loading: true (just to show the user something while they wait), then fires off an async call. When the async call finished, the state is updated to set loading: false and set the result of the async call to a value in the state. When the state is updated, the render function is automatically called and the UI is updated to reflect the state change.
const fakePromise = () =>
new Promise((resolve, reject) => {
const fakeResult = "Complete";
setTimeout(() => resolve(fakeResult), 1000);
});
class App extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
loading: false,
result: null
};
this.startAsync = this.startAsync.bind(this);
}
startAsync() {
this.setState({
loading: true
});
fakePromise().then(result =>
this.setState({
loading: false,
result
})
);
}
render() {
const { loading, result } = this.state;
return (
<div className="App">
{!result &&
!loading && (
<div>
<h1>Result Not Fetched</h1>
<button onClick={this.startAsync} type="button">
Fetch Result Async
</button>
</div>
)}
{loading && <h1>Fetching Result</h1>}
{result && <h1>Result is: {result}</h1>}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<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>
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>
);
}