API taking too long, map function firing before data loads - javascript

import React, { Component } from 'react';
import {withProvider} from './TProvider'
import ThreeCardMap from './ThreeCardMap';
class Threecard extends Component {
constructor() {
super();
this.state = {
newlist: []
}
}
componentDidMount(){
this.props.getList()
this.setState({newlist: [this.props.list]})
}
// componentDidUpdate() {
// console.log(this.state.newlist);
// }
render() {
const MappedTarot = (this.state.newlist.map((list, i) => <ThreeCardMap key={i} name={list.name} meaningup={list.meaning_up} meaningdown={list.meaning_rev}/>);
return (
<div>
<h1>Three Card Reading</h1>
<div>{ MappedTarot }</div>
</div>
)
}
}
export default withProvider(Threecard);
Hi, I'm trying to create a page that takes data from a tarot card API (https://rws-cards-api.herokuapp.com/api/v1/cards/search?type=major). Unfortunately by the time the data comes in, my map function has already fired. I'm asking to see if there is a way to have the map function wait until the data hits before it fires. Thanks!
Edit: getList function in the Context:
getList = () => {
console.log('fired')
axios.get('https://vschool-cors.herokuapp.com?url=https://rws-cards-api.herokuapp.com/api/v1/cards/search?type=major').then(response =>{
this.setState({
list: response.data
})
}).catch(error => {
console.log(error);
})
}

this.props.getList() is an async function. You are setting the list right after that call which is not correct.
You need to set it in the getList promise then() block.

getList() is an async function and update data for the parent component. So, my solution is just watching the list from the parent component if they updated or not, through getDerivedStateFromProps
class Threecard extends Component {
constructor() {
super();
this.state = {
newlist: []
}
}
// Set props.list to this.state.newList and watch the change to update
static getDerivedStateFromProps(nextProps, prevState) {
return {
newlist: nextProps.list
}
}
componentDidMount(){
this.props.getList()
// Removed this.setState() from here.
}
render() {
const MappedTarot = (this.state.newlist.map((list, i) => <ThreeCardMap key={i} name={list.name} meaningup={list.meaning_up} meaningdown={list.meaning_rev}/>);
return (
<div>
<h1>Three Card Reading</h1>
<div>{ MappedTarot }</div>
</div>
)
}
}
export default withProvider(Threecard);

Related

How to do a for loop with React and why is my Component not getting rendered

import React, { Component } from "react";
import Pokecard from "./Pokecard";
import "./Pokedex.css";
class Pokedex extends Component {
static defaultProps = {
pokemon: [],
getData() {
for (let i = 1; i <= 40; i++) {
fetch(`https://pokeapi.co/api/v2/pokemon/${i}`)
.then((response) => response.json())
.then((data) => {
this.pokemon.push({
id: data.id,
namePoke: data.name,
type: data.types[0].type.name,
base_experience: data.base_experience,
});
});
}
},
};
render() {
this.props.getData();
return (
<div className="Pokedex">
<div className="Pokedex-grid">
{this.props.pokemon.map((p) => (
<Pokecard
id={p.id}
name={p.namePoke}
type={p.type}
exp={p.base_experience}
key={p.id}
/>
))}
</div>
</div>
);
}
}
export default Pokedex;
I am new to React and I don't understand why my for loop runs multiple times so I get the Pokecards twice or more.
Also, the whole Component Pokedex is not showing up when reloading the page. What do I do wrong?
#azium made a great comment. You are calling to get data in your render, which is setting state, and causing a re-render, which is calling getData again, which is fetching data again and then setting state again, and the cycle continues on and on indefinitely. Also, default props should only define properties default values, but in this case you don't need a getData default prop. All you need to do is call the getData method in your componentDidMount. And your method needs to store the data in state, and not do a direct property change (like you are doing). Here is an example:
import React, { Component } from "react";
import Pokecard from "./Pokecard";
import "./Pokedex.css";
class Pokedex extends Component {
static state = {
pokemon: []
};
componentDidMount() {
this.getData();
}
getData() {
for (let i = 1; i <= 40; i++) {
const pokemon = [...this.state.pokemon];
fetch(`https://pokeapi.co/api/v2/pokemon/${i}`)
.then((response) => response.json())
.then((data) => {
pokemon.push({
id: data.id,
namePoke: data.name,
type: data.types[0].type.name,
base_experience: data.base_experience,
});
});
this.setState({pokemon});
}
}
render() {
return (
<div className="Pokedex">
<div className="Pokedex-grid">
{this.state.pokemon.map((p) => (
<Pokecard
id={p.id}
name={p.namePoke}
type={p.type}
exp={p.base_experience}
key={p.id}
/>
))}
</div>
</div>
);
}
}
export default Pokedex;

Props are showing empty value above `render` function while it work inside `render` function?

So I am passing props from app.js to state.jsx but while console logging the props it give me a undefined but it perfectly works inside the render function. Why that's happening?
App.js
class App extends React.Component{
state = {
data : {},
states: '',
}
async componentDidMount () {
const dataFromApi = await StateData();
this.setState({ data: dataFromApi})
}
handleStateChange = async(states) => {
const fetchedData = await StateData(states);
this.setState({data: fetchedData, states: states})
}
render(){
const {data, states} = this.state;
console.log(data);
return (
<div className="App">
<StateCard data={data} states={states}/>
</div>
);
}
}
State.jsx
export default class StateCard extends React.Component{
constructor(props) {
//this doesn't work
super(props)
const dta = this.props.data.confirmed
console.log(dta)
}
// spacing deafult value is 8px , so the 3*8=24px width column
render(){
//This works
console.log(this.props.data.confirmed)
console.log(this.props.states)
const {confirmed,active, deaths, recovered} = this.props.data
return (
<div>
<span>Confirmed : {confirmed}</span>
</div>);}
}
you can use useEffect and Function component instead of React.Component.
it's more easier then using componentdidMount and other lifecycle methods.
import React, { useEffect } from "react";
const StateCard = props => {
const { data, states } = props;
const { confirmed, active, deaths, recovered } = data;
useEffect(() => {
console.log(confirmed);
console.log(states);
}, [props]);
return (
<div>
<span>Confirmed : {confirmed}</span>
</div>
);
};
export default StateCard;

Consuming Paginated API in React Component

I'm just getting started with React. As a simple exercise, I wanted to create some components for viewing data retrieved from the JsonMonk API. The API contains 83 user records and serves them in pages of 10.
I am trying to develop a component for viewing a list of users one page at a time which I called UserList. The code for it is below:
class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 1,
users: [],
};
this.onPageNext = this.onPageNext.bind(this);
}
componentDidMount() {
this.fetchUsers(this.state.pageNumber)
.then((users) => this.setState({users: users}));
}
async fetchUsers(pageNumber) {
const response = await fetch(`https://jsonmonk.com/api/v1/users?page=${pageNumber}`);
const jsonResponse = await response.json();
return jsonResponse.data.records;
}
onPageNext() {
// ...
}
render() {
const postElements = this.state.users.map(
(props) => <User key={props._id} {...props} />);
return (
<div>
{postElements}
<div>
<button onClick={this.onPageNext}>Next</button>
</div>
</div>
);
}
}
The problem I am having pertains to the onPageNext method of my component. When the user clicks the "Next" button, I want to make a fetch for the next page of data and update the list.
My first attempt used an asynchronous arrow function passed to setState like so:
onPageNext() {
this.setState(async (state, props) => {
const nextPageNumber = state.pageNumber + 1;
const users = await this.fetchUsers(nextPageNumber);
return {pageNumber: nextPageNumber, users: users}
})
}
However, it does not seem React supports this behavior because the state is never updated.
Next, I tried to use promise .then syntax like so:
onPageNext() {
const nextPageNumber = this.state.pageNumber + 1;
this.fetchUsers(nextPageNumber)
.then((users) => this.setState({pageNumber: nextPageNumber, users: users}));
}
This works but the problem here is that I am accessing the class's state directly and not through setState's argument so I may receive an incorrect value. Say the user clicks the "Next" button three times quickly, they may not advance three pages.
I have essentially run into a chicken-or-the-egg type problem. I need to pass a callback to setState but I need to know the next page ID to fetch the data which requires calling setState. After studying the docs, I feel like the solution is moving the fetch logic out of the UsersList component, but I'm not entirely sure how to attack it.
As always, any help is appreciated.
You need to change onPageNext as below:
onPageNext() {
this.setState( prevState => {
return {pageNumber: prevState.pageNumber + 1}
}, () =>{
this.fetchUsers(this.state.pageNumber).then(users => this.setState({users: users}) )
});
}
Here is the Complete Code:
import React from "react";
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 1,
users: [],
};
this.onPageNext = this.onPageNext.bind(this);
}
componentDidMount() {
this.fetchUsers(this.state.pageNumber)
.then((users) => {
console.log(users, 'users');
this.setState({users: users})
}
);
}
async fetchUsers(pageNumber) {
const response = await fetch(`https://jsonmonk.com/api/v1/users?page=${pageNumber}`);
const jsonResponse = await response.json();
return jsonResponse.data.records;
}
onPageNext() {
this.setState( prevState => {
return {pageNumber: prevState.pageNumber + 1}
}, () =>{
this.fetchUsers(this.state.pageNumber).then(users => this.setState({users: users}) )
});
}
render() {
const postElements = this.state.users.map(
(user) => <User key={user._id} {...user} />);
return (
<div>
{postElements}
<div>
<button onClick={this.onPageNext}>Next</button>
</div>
</div>
);
}
}
function User(props) {
return (
<div>
<div style={{padding: 5}}>Name: {props.first_name} {props.last_name}</div>
<div style={{padding: 5}}>Email: {props.email}</div>
<div style={{padding: 5}}>Phone: {props.mobile_no}</div>
<hr/>
</div>
);
}
Here is the Code Sandbox

ReactJs - How to complete onClick before download - href

I have a simple React button component that when clicked should retrieve and download data on the client browser. The problem I am experiencing is that the download is triggered and the csv file downloaded before the data is passed into the href.
Here is my component:
import { Component } from 'react';
import { connect } from 'react-redux';
import { PropTypes } from 'prop-types';
import { ManageUsersSelectors } from 'selectors/Users';
import { BatchRoleActions } from 'actions/Users';
class UsersExportButton extends Component {
constructor() {
super();
this.state = {
users: ''
};
}
getUsers(){
const { userIds } = this.props;
BatchRoleActions.getAllRoleUsers(userIds)
.then((users) => {
this.setState({ users: users});
return this.state.users;
});
}
render() {
return (
<div className="roles-export-button">
<a className="button button-default" href={this.state.users} download={'roles.csv'} onClick={() => this.getUsers()} return true>Export Csv</a>
</div>
);
}
}
function mapStateToProps(state) {
const userIds = ManageUsersSelectors.batchUserIdsSelector(state);
return {
userIds: userIds
};
}
UsersExportButton.propTypes = {
text: PropTypes.string.isRequired,
data: PropTypes.array
};
export default connect(mapStateToProps)(UsersExportButton);
How can I get the getUsers()/onClick function to complete the data retrieval step before downloading?
When i debug my code I can see that the getUsers function returns data - however after the download is triggered
Make sure to bind this to your functions. In your constructor you can do:
constructor() {
super();
this.state = {
users: ''
};
this.getUsers = this.getUsers.bind(this);
}
or you can use the bind this function:
getUsers = () => {
const { userIds } = this.props;
BatchRoleActions.getAllRoleUsers(userIds)
.then((users) => {
this.setState({ users: users});
return this.state.users; // This should be removed, you can use this.state.users throughout this component.
});
}
Why not get the user data in the componentDidMount lifecycle method? It doesn't look like it needs to be called onClick.
{
// ...
componentDidMount() {
this.getUsers();
}
// ...
render() {
return (
<div className="roles-export-button">
<a className="button button-default" href={this.state.users} download={'roles.csv'}>Export Csv</a>
</div>
)
}
}
How about handling the default "link" behaviour manually to get more control? Also you should probably try to access state after setState has been executed via its callback.
e.g.
getUsers(cb){
const { userIds } = this.props;
BatchRoleActions.getAllRoleUsers(userIds)
.then((users) => {
// note the callback of setState which is invoked
// when this.state has been set
this.setState({ users: users }, cb);
});
}
const handleClick = () => {
this.getUsers(() => {
window.open(this.state.whatever)
})
}
<span onClick={handleClick}>Export Csv</span>

React.js - Loading single post data from API correctly

I am fairly new to React, and trying to work my way through how I should properly be loading data from my API for a single post.
I have read that I should be using "componentDidMount" to make my GET request to the API, but the request is not finished by the time the component renders. So my code below does not work, as I am recieving the error: "Cannot read property setState of undefined".
What I am doing wrong here? Should I be calling setState from somewhere else? My simple component is below - thanks.
import React from 'react';
import Header from './Header';
import axios from 'axios';
class SingleListing extends React.Component {
constructor(props) {
super(props);
this.state = {
listingData: {}
}
}
componentDidMount() {
// Get ID from URL
var URLsegments = this.props.location.pathname.slice(1).split('/');
// Load the listing data
axios.get('/api/listing/' + URLsegments[1])
.then(function(res){
let listingDataObject = res.data;
console.log(listingDataObject);
this.setState({
listingData: listingDataObject
});
})
.catch(function(err){
console.log(err);
});
}
render() {
console.log('helsdfdsfsdflssosso');
console.log(this.state.listingData);
return (
<div className="SingleListing">
<Header />
<div className="container">
<div>Property Address: {this.state.listingData.propertyAddress}</div>
This is a single listing
</div>
</div>
)
}
}
export default SingleListing;
You just need to change what you render depending on whether the data is loaded or not yet.
Also, you should use arrow functions when handling the axios response, otherwise this is not set correctly.
class SingleListing extends React.Component {
constructor(props) {
super(props);
this.state = {
listingData: null,
};
}
componentDidMount() {
// Get ID from URL
const URLsegments = this.props.location.pathname.slice(1).split('/');
// Load the listing data
axios
.get(`/api/listing/${URLsegments[1]}`)
.then(res => {
const listingDataObject = res.data;
console.log(listingDataObject);
this.setState({
listingData: listingDataObject,
});
})
.catch(err => {
console.log(err);
});
}
render() {
const isDataLoaded = this.state.listingData;
if (!isDataLoaded) {
return <div>Loading...</div>;
}
return (
<div className="SingleListing">
<Header />
<div className="container">
<div>Property Address: {this.state.listingData.propertyAddress}</div>
This is a single listing
</div>
</div>
);
}
}
export default SingleListing;
this is out of scope you need to include it. here is a solution using es2015 arrow functions =>
axios.get('/api/listing/' + URLsegments[1])
.then((res) => {
let listingDataObject = res.data;
console.log(listingDataObject);
this.setState({
listingData: listingDataObject
});
})
.catch((err) => {
console.log(err);
});

Categories

Resources