ReactJS - Pagination/Load More from a REST API - javascript

In Short: I am fetching X JSON Objects from an API, but I only want to display the first 20 Objects, and then have the possibility to fetch another 20 Objects if the user decides to. Whats the best way to do that?
Here is my component, where I am fetching all the teams on componentDidMount and then getting all the JSON objects from my reducer:
import React, { Component } from 'react'
import { APIManager, DateUtils, YouTubeID } from '../../utils'
import actions from '../../actions'
import { connect } from 'react-redux'
import { Link, browserHistory } from 'react-router'
class teams extends Component {
constructor () {
super()
this.state = {
}
}
selectTeam(team, event){
event.preventDefault()
this.props.selectTeam(team)
}
componentDidMount(){
if(this.props.teams != null){
return
}
this.props.fetchTeams()
}
componentDidUpdate(){
if(this.props.teams != null){
return
}
this.props.fetchTeams()
}
render(){
return(
<div id="wrap">
<div style={{height: '65px'}} className="hero hero-teams-2">
<div className="hero-inner group">
<h1 className="center title-long">
<strong>Pro CS:GO </strong>
Teams
</h1>
</div>
</div>
<div style={{paddingTop: '0px'}} id="wrap-inner">
<div id="content" role="main">
<div id="main" className="main-full">
<div className="group">
<div style={{marginLeft: '150px'}} className="portal-group designers">
<h3 style={{marginLeft: '40%'}} className="jump">
Select A Team
</h3>
<div className="scrollable-content-container">
<ol className="portal-list-members debutants scrollable">
{(this.props.teams == null) ? null :
this.props.teams.map((team, i) => {
return (
<li onClick={this.selectTeam.bind(this, team.teamname)} key={i} className="group">
<h3>
<Link to={'/team'} style={{color: '#444', textTransform: 'capitalize'}} className="url hoverable" href="#">
<img style={{height: '160px', width: '160px'}} className="photo" src={"images/"+team.image}/>
{team.teamname}
</Link>
</h3>
</li>
)
})
}
</ol>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
}
const stateToProps = (state) => {
return {
teams: state.players.teams
}
}
const dispatchToProps = (dispatch) => {
return {
selectTeam: (team) => dispatch(actions.selectTeam(team)),
fetchTeams: (params) => dispatch(actions.fetchTeams(params))
}
}
export default connect(stateToProps, dispatchToProps)(teams)
Mongo Query
router.get('/liveTeams', function(req, res, next){
Team.find({})
.where('status', 'live')
.sort({timestamp : -1})
.exec(function(err, results){
if(err) return next(err);
res.json({
confirmation: 'success',
results: results
})
});
});

MongoDB has a extra method: .limit(20) you can add so you only get max 20 records back.
You can then also use the .skip() method to skip over the previous records.
So if the sorting is Always the same, you would use:
Team.find({}).where('status', 'live').sort({timestamp : -1}).skip(0).limit(20)
the first time.
The next call will be:
Team.find({}).where('status', 'live').sort({timestamp : -1}).skip(20).limit(20)
and so on.

Related

Next.js getStaticProps not refreshing the data [duplicate]

This question already has an answer here:
How to add new pages without rebuilding an app with +150k static pages?
(1 answer)
Closed 11 months ago.
I've jus started working and learning Next so I have a lot of confusions,
I was using the useEffect on react and it always updated the UI with the new stuff that was added to the API however, its not working on next.js
SO I have an index file
import Link from "next/link";
import react, {useState, useEffect} from "react";
import { useRouter } from 'next/router';
export async function getStaticProps({ res }) {
try {
const result = await fetch(`https://api.pandascore.co/matches/running??sort=&page=1&per_page=10&&token=#`);
const data = await result.json();
return {
props: { game: data },
revalidate: 10 // 10 seconds
};
} catch (error) {
res.statusCode = 404;
return { props: {} };
}
}
const upcomingGames = ({ game }) => {
return (
<div className="container">
<h2>Live Games - </h2>
<div className="columns is-multiline">
{game.map(q => (
<div className="column is-half" key={q.id}>
<div className="inner">
<div className="inner__box">
<Link href = {'/live/' + q.slug} key={q.slug}>
<a className="h2link" key={q.slug}> {q.name}</a>
</Link></div>
</div>
</div>
))}
</div>
</div>
);
}
export default upcomingGames;
This file is connected to a [slug].js file which displays more details about a game,
Now in production when I deployed the app to vercel I have noticed that when a new game is added to the API it displays in the index.js but when I click on it I'm redirected to a fallback(404) page.
After I redeploy my project this is fixed, however every time a new game is added and rendered I'm unable to access its individual page which I defined in [slug].js
export const getStaticPaths = async () => {
const res = await fetch(`https://api.pandascore.co/matches/running?sort=&page=1&per_page=50&token=#`);
const data = await res.json();
const paths = data.map(o => {
return {
params: { slug: o.slug.toString() }
}
})
return {
paths,
fallback: false
}
}
export const getStaticProps = async (context) => {
const slug = context.params.slug;
const res = await fetch(`https://api.pandascore.co/matches/running?search[slug]=${slug}&token=#`);
const data = await res.json();
console.log(data)
return {
props: {
game: data
}
}
}
export default function live({ game }) {
return (
<div className="container">
<h2> Single Game deets.</h2>
{game.map((g) => (
<div className="container" key={g.id} >
<div className="inner-box" key={g.slug}>
{/** Fetch team and display their corresponding score - A bit of code repition :( */}
<div className="score-board-min columns is-mobile is-multiline">
<div className="column is-full"> {g.opponents.slice(0, -1).map((o) => <span className="team" key={o.id}>{o.opponent.name}</span>)}
{g.results.slice(0, -1).map((res, i) => (
<span className="scores" key={i}>{res.score}</span>
))}</div>
<div className="column">
{g.opponents.slice(-1).map((o) => <span className="team" key={o.id}>{o.opponent.name}</span>)}
{g.results.slice(-1).map((res, i) => (
<span className="scores" key={i}><div>{res.score}</div></span>
))}
</div>
</div>
<br />
<div className="lower-box columns is-multine">
<div className="column is-half">
<div className="dark"><span className="is-pulled-left">League</span> <span className="is-pulled-right">{g.league && g.league.name}</span></div>
<div className="dark"><span className="is-pulled-left">Game:</span> <span className="is-pulled-right"> {g.videogame && g.videogame.name} </span></div>
<div className="dark alt"><span className="is-pulled-left">Tournament</span> <span className="is-pulled-right"> {g.tournament && g.tournament.name} | </span></div>
<div className="dark"><span className="is-pulled-left">Series</span> <span className="is-pulled-right"> {g.serie.full_name} | {g.serie.tier.toUpperCase()} </span></div>
<div className="dark alt"><span className="is-pulled-left">Teams</span> <span className="is-pulled-right"> {g.opponents.map((o) => o.opponent.name).join(" vs ")} </span></div>
</div>
</div>
<br />
</div>
</div>
))}
</div>
)
}
During development (next dev) getStaticPaths gets called on every request, but for production it only gets called the next time you run next build. So when a new game is added to the API, the paths named after ${some_new_game_slug} won't exist until you run next build again, i.e., re-deploy. If this type of data changes frequently, you might have to use getServerSideProps for [slug].js as well (so no static paths) or opt for the client-side data fetching approach.

Uncaught TypeError: Cannot read properties of undefined (reading 'map')

I am getting this error
Mainsection.js:27 Uncaught TypeError: Cannot read properties of undefined (reading 'map')
Here is my mainsection.js file, I am using API key to iterate the data, I am still not getting the cause of the error. First I had made an array whose name was info and stored all the data in it and then iterated it, Now after using fetchapi, I deleted that array as it was of no use. I don't know whether it was meant to delete or not.
import React, { Component } from 'react'
import Card from './Card'
export default class Mainsection extends Component {
constructor() {
super();
this.state = {
info:null
}
}
async componentDidMount(){
let Url="https://randomuser.me/api/?inc=gender,name,nat,location,picture,email&results=";
let data= await fetch(Url);
let parsedData= await data.json()
console.log(parsedData);
this.setState({
info : parsedData.info
})
}
render() {
return (
<div>
<div className="container mt-5">
<div className="row">
{this.state.info.map((element) => {
return <div className="col-md-4">
<Card key={element.email} name={element.name} location={element.location} gender={element.gender} imageUrl={element.picture.medium} />
</div>
})}
</div>
</div>
</div>
)
}
}
And here is my card.js file
import React, { Component } from 'react'
export default class Card extends Component {
render() {
let {name, location, gender, imageUrl}=this.props
return (
<div>
<div className="card" style={{ width: "18rem" }}>
<img src={imageUrl} className="card-img-top" alt="..." />
<div className="card-body">
<h5 className="card-title">{name}</h5>
<p className="card-text">{location}</p>
<p className="card-text">{gender}</p>
Go somewhere
</div>
</div>
</div>
)
}
}
Please let me I should provide more details
You can find a working example here: https://codesandbox.io/s/musing-hill-uxtt0
There are other issues with your code. For example, the name and location are objects and you are directly trying to show it on UI. I have also added code to fix name.
Mainsection.js
import React, { Component } from "react";
import Card from "./Card";
export default class Mainsection extends Component {
constructor() {
super();
this.state = {
info: null,
results: null
};
}
async componentDidMount() {
let Url =
"https://randomuser.me/api/?inc=gender,name,nat,location,picture,email&results=";
let data = await fetch(Url);
let parsedData = await data.json();
console.log(parsedData);
this.setState({
info: parsedData.info,
results: parsedData.results
});
}
render() {
console.log("results : ", this.state.results);
return (
<div>
<div className="container mt-5">
<div className="row">
{this.state?.results?.map((element) => {
return (
<div className="col-md-4">
<Card
key={element.email}
name={element.name}
location={element.location}
gender={element.gender}
imageUrl={element.picture.medium}
/>
</div>
);
})}
</div>
</div>
</div>
);
}
}
Card.js
import React, { Component } from "react";
export default class Card extends Component {
render() {
let { name, location, gender, imageUrl } = this.props;
return (
<div>
<div className="card" style={{ width: "18rem" }}>
<img src={imageUrl} className="card-img-top" alt="..." />
<div className="card-body">
<h5 className="card-title">{`${name?.title} ${name?.first} ${name?.last}`}</h5>
<p className="card-text">{JSON.stringify(location)}</p>
<p className="card-text">{gender}</p>
<a href="/" className="btn btn-primary">
Go somewhere
</a>
</div>
</div>
</div>
);
}
}
So, the API you are using doesn't response back with an Array. But it responses with an object, that object has 2 attributes which are results and info. as you can see below
{
"results": ...
....
....
"info": ...
}
and results it self is an Array of objects, while info is just an object.
So yes, you cant use map on object, its only useable on arrays.
Check the response carefully so you can decide what you wanna do.

How to render Array stored in a Object in React?

I am trying to develop a discussion forum website using React, Node and MongoDB.In post object, there is nested author object and tags array.
Here is sample image of a post object:
here is the component which I am trying to render:
import React, { Component } from "react";
import http from "../services/httpService";
import { postEndPoint, repliesEndPoint } from "../config.json";
class PostPage extends Component {
state = {
post: [],
replies: [],
};
async componentDidMount() {
const id = this.props.match.params.id;
const { data: post } = await http.get(postEndPoint + "/" + id);
const { data: replies } = await http.get(repliesEndPoint + "/" + id);
console.log(post.tags, typeof post.tags);
this.setState({ post: post, replies: replies });
}
render() {
const { post, replies } = this.state;
return (
<React.Fragment>
<div className="container col-lg-8 shadow-lg p-3 mt-5 bg-body rounded">
<h2>{post.title}</h2>
<p className="mt-4" style={{ color: "#505050" }}>
{post.description}
</p>
<div className="mt-1">
Related Topics:
{post.tags.map((tag) => (
<span className="badge badge-secondary m-1 p-2">
{(tag).name}
</span>
))}
<h6 className="mt-2">
{post.upvotes.length} Likes {post.views} Views
</h6>
<div class="d-flex w-100 justify-content-between">
<small class="mb-1">Posted by {post.author['name']}</small>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default PostPage;
This throws the following : TypeError: post.tags is undefined. a Similar error is throws while accessing post.upvotes and post.author
Since you do your http request in 'componentDidMount' a render occured at least once before. So react tried to read post.something and it was still undefined.
And even if you do it before an http request is asynchronous so be careful
You need to check that post.something is defined before you use.
Also your initialisation if confusing you initialize post as an array but you are trying to do post.title.
If post is really an array then post.map() won't crash on an empty array.
If it's an object check that is it defined correctly.
Try this as initial state
state = {
post: {
description:"",
title:"",
tags: [],
author:[] ,
upvotes:[] ,
views : 0
},
}
initial state for post is {}
state = {
post: { tags: [] },
replies: [],
};
You can have a simple if condition added. So it will only loop through that if it is present. Check this.
import React, { Component } from "react";
import http from "../services/httpService";
import { postEndPoint, repliesEndPoint } from "../config.json";
class PostPage extends Component {
state = {
post: [],
replies: [],
};
async componentDidMount() {
const id = this.props.match.params.id;
const { data: post } = await http.get(postEndPoint + "/" + id);
const { data: replies } = await http.get(repliesEndPoint + "/" + id);
console.log(post.tags, typeof post.tags);
this.setState({ post: post, replies: replies });
}
render() {
const { post, replies } = this.state;
return (
<React.Fragment>
<div className="container col-lg-8 shadow-lg p-3 mt-5 bg-body rounded">
<h2>{post.title}</h2>
<p className="mt-4" style={{ color: "#505050" }}>
{post.description}
</p>
<div className="mt-1">
Related Topics:
{post.tags && post.tags.map((tag) => ( // <--- map will only execute when it finds tags.
<span className="badge badge-secondary m-1 p-2">
{(tag).name}
</span>
))}
<h6 className="mt-2">
{(post.upvotes && post.upvotes.length) || 0} Likes {post.views} Views // <---- These default values too will handle the case where the data isnt ready yet
</h6>
<div class="d-flex w-100 justify-content-between">
<small class="mb-1">Posted by {post.author['name']}</small>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default PostPage;

I have to select the 'Search' button twice before the correct results are rendered - React JS

Apologies if this question has been asked before and solved, but I have been searching and haven't had much luck.
I have created an app where a person can search for a book and the results are returned. The problem I am having is when I enter a book title and select the search button an empty array is rendered and then an array with the proper results are returned but not rendered. When I select the search button again the proper results are rendered. I would like the correct results to be rendered on the first click.
I logged the results and it looks like the empty array is being rendered first instead of the correct results. I believe it could be due to the async nature of the API call.
Here is my code:
class App extends Component {
state = {
data: []
}
handleBookSearch = (book) => {
let data = utils.searchBooks(book);
this.setState({ data: data });
}
render() {
return (
<div className="">
<Banner
onSearch={this.handleBookSearch}
onRender={this.renderBooks} />
<div className="custom-margin">
<BookDisplay bookData={this.state.data} />
</div>
</div>
);
}
}
export default App;
class PostDisplay extends Component {
render() {
const { bookData } = this.props;
return (
bookData.map(book => (
<div>
<div className="cap-color">
<h4 className="card-header"><i className="fas fa-book fa-fw"></i>{book.title}</h4>
</div>
<div className="card-body">
<img src={book.thumbnail} alt={book.title} />
<h5 className="card-title margin-above"><b>Author:</b> {book.authors}</h5>
<h6><b>Publisher:</b> {book.publisher}</h6>
<h6><b>Published On:</b> {book.publishedDate}</h6>
<h6><b>Supported Languages:</b> {book.language}</h6>
<p className="card-text"><b>Description:</b> {book.description}</p>
<a href={book.link} target="_blank" rel="noopener noreferrer"
className="btn btn-primary cap-theme-project">Buy the book!</a>
</div>
</div>
))
);
}
}
export default PostDisplay;
class Banner extends Component {
constructor() {
super();
this.state = {
search: ''
};
}
updateSearch(event) {
const searchParam = event.target.value;
this.setState({ search: searchParam });
}
render() {
return (
< div className="title-banner" >
<h1 className="header-padding"><i className="fas fa-book fa-fw"></i><b>Google Library</b></h1>
<div className="container">
<div className="row justify-content-center">
<div className="col-12 col-md-10 col-lg-8 opacity">
<div className="card-body row no-gutters align-items-center">
<div className="col">
<input className="form-control form-control-lg form-control-borderless"
type="text" placeholder="Search for a book..." ref="searchQuery"
value={this.state.search}
onChange={this.updateSearch.bind(this)} />
</div>
<div className="col-auto">
<button onClick={() => this.props.onSearch(this.state.search)}
className="btn-margin btn btn-lg btn-primary"><i class="fas fa-search"></i></button>
</div>
</div>
</div>
</div>
</div>
</div >
);
}
}
export default Banner;
var books = require('google-books-search');
export let data = [];
export function searchBooks(title) {
books.search(title, function (err, results) {
if (!err) {
data = results;
} else {
console.log('ERROR: ' + err);
}
});
return data;
}
Use callback function. It will set state after callback method called.
handleBookSearch = (book) => {
let data = utils.searchBooks(book,(data)=>{
this.setState({ data: data });
});
}
export function searchBooks(title,callback) {
books.search(title, function (err, results) {
if (!err) {
callback(results);
} else {
console.log('ERROR: ' + err);
}
});
}
Yes your assumption is correct it is due to async operation. Handle promise like this.
handleBookSearch = async (book) => {
let data = await utils.searchBooks(book);
this.setState({ data: data });
}

react expand and collapse just one panel

Need help with react...
Trying to implement a collapsible list of cards with weather information.
Already implemented the behavior of expand and collapse, but when i clicked on one panel the other panel open at the same time (i have 2 panels and need 7 to display weahter for 7 days of the week).
How can i open and close just one panel?
Code:
import React, { Component } from 'react';
import Moment from 'react-moment';
import RandomGif from './RandomGif.js';
const urlForCity = city => `https://cors-anywhere.herokuapp.com/http://api.openweathermap.org/data/2.5/forecast/daily?q=${city}&units=metric&cnt=7&appid=1fba7c3eaa869008374898c6a606fe3e`
class OpenWapi extends Component {
constructor(props) {
super(props);
this.state = {
requestFailed: false,
shown: false
}
this.componentDidMount = this.componentDidMount.bind(this);
this.toggle = this.toggle.bind(this);
}
componentDidMount() {
fetch(urlForCity(this.props.city))
.then(response => {
if(!response.ok) {
throw Error("Network request failed")
}
return response;
})
.then(data => data.json())
.then(data => {
this.setState({
weatherData: data
})
}, () => {
this.setState({
requestFailed: true
})
})
}
toggle() {
this.setState({
shown: !this.state.shown
});
}
render() {
if(this.state.requestFailed) return <p>Request Failed.</p>;
if(!this.state.weatherData) return <p>Loading...</p>;
return (
<div>
<p>City: {this.state.weatherData.city.name}</p>
{/* Day 1 */}
<div onClick={this.toggle} className="dayWeekItem">
<div className="top-content">
<div className="icon-weather"></div>
<div className="date">
<div className="weekday">Today</div>
<div className="day-long"><Moment unix format="MMM DD YYYY">{this.state.weatherData.list[0].dt}</Moment></div>
</div>
<div className="temperature">
<div className="temp-high">{parseInt(this.state.weatherData.list[0].temp.max)}º</div>
<div className="temp-low">{parseInt(this.state.weatherData.list[0].temp.min)}º</div>
</div>
</div>
<div className={this.state.shown ? "toggleContent-open" : "toggleContent-closed"} >
<div className="weather-gif" >
<RandomGif keyword={this.state.weatherData.list[0].weather[0].description} />
</div>
</div>
</div>
{/* Day 2 */}
<div onClick={this.toggle} className="dayWeekItem">
<div className="top-content">
<div className="icon-weather"></div>
<div className="date">
<div className="weekday">Tomorrow</div>
<div className="day-long"><Moment unix format="MMM DD YYYY">{this.state.weatherData.list[1].dt}</Moment></div>
</div>
<div className="temperature">
<div className="temp-high">{parseInt(this.state.weatherData.list[1].temp.max)}º</div>
<div className="temp-low">{parseInt(this.state.weatherData.list[1].temp.min)}º</div>
</div>
</div>
<div className={this.state.shown ? "toggleContent-open" : "toggleContent-closed"} >
<div className="weather-gif" >
<RandomGif keyword={this.state.weatherData.list[1].weather[0].description} />
</div>
</div>
</div>
{/* Day 3 */}
{/* Day 4 */}
{/* Day 5 */}
</div>
)
}
}
export default OpenWapi;
I would have an object to represent the state, a field for each panel.
Like this:
constructor(props) {
...
this.state = {
requestFailed: false,
shown: {}
}
...
}
...
toggle(panelNumber) {
this.setState({
shown: {
...this.state.shown,
[panelNumber]: !this.state.shown[panelNumber]
}
});
}
...
The toogle function is used like this, for instance, Day 1:
<div onClick={() => this.toggle(1)} className="dayWeekItem">
...
</div>
And to show in html, for instance, Day 1:
<div className={this.state.shown[1] ? "toggleContent-open" : "toggleContent-closed"} >
<div className="weather-gif" >
<RandomGif keyword={this.state.weatherData.list[0].weather[0].description} />
</div>
</div>
They all will collapse always with your implementation.
You have a state
state = {
shown: true
}
You have a function to toggle it
toggle = () => {
this.setState(shown: !this.state.shown)
}
And you render the component, using the this.state.shown in two places, but the value will always be one true or false
render() {
return(<div .....//something>
<div onClick={this.toggle}>
{ this.state.shown ? <SomeComponent or HTML Tag> : null }
</div>
<div onClick={this.toggle}>
{ this.state.shown ? <SomeComponent or HTML Tag> : null }
</div>
</div>)
}
So where ever you toggle, once the state is updated and render method is called again to paint the view, both sections of divs get the sameBoolean` value. Therefore, they both collapse.
Best Solution I can offer for this problem will be:
Create a separate component which has two jobs to be do:
1. Maintains its own state, of collapse true or false.
2. Render the children given to it without wondering what they might be.
So let say
class WeatherWidget extends React.PureComponent {
state= {
shown: true
}
toggle = () => this.setState({shown: !this.state.shown})
render() {
return(
<div onClick={this.toggle} className="dayWeekItem">
<div className="top-content">
<div className="icon-weather"></div>
<div className="date">
<div className="weekday">Today</div>
<div className="day-long">
<Moment unix format="MMM DD YYYY">{this.props.date}</Moment>
</div>
</div>
<div className="temperature">
<div className="temp-high">{parseInt(this.props.maxTemp)}º
</div>
<div className="temp-low">{parseInt(this.props.minTemp)}º
</div>
</div>
</div>
<div className={this.state.shown ? "toggleContent-open" : "toggleContent-closed"} >
<div className="weather-gif" >
<RandomGif keyword={this.props.gifDescription} />
</div>
</div>
</div>
)
}
}
So you create a reusable component which manages its own state ( React Paradigm/ Composition brings reusability)
As for displaying multiple widgets
class OpenWapi extends Component {
constructor(props) {
super(props);
this.state = {
requestFailed: false,
shown: false
}
this.componentDidMount = this.componentDidMount.bind(this);
this.toggle = this.toggle.bind(this);
}
componentDidMount() {
fetch(urlForCity(this.props.city))
.then(response => {
if(!response.ok) {
throw Error("Network request failed")
}
return response;
})
.then(data => data.json())
.then(data => {
this.setState({
weatherData: data
})
}, () => {
this.setState({
requestFailed: true
})
})
}
render() {
if(this.state.requestFailed) return <p>Request Failed.</p>;
if(!this.state.weatherData) return <p>Loading...</p>;
return(
<div>
<p>City: {this.state.weatherData.city.name}</p>
<WeatherWidget
date={this.state.weatherData.list[0].dt}
maxTemp={this.state.weatherData.list[0].temp.max}
minTemp={this.state.weatherData.list[0].temp.min}
gifDescription=
{this.state.weatherData.list[0].weather[1].description}
/>
<WeatherWidget
date={this.state.weatherData.list[1].dt}
maxTemp={this.state.weatherData.list[1].temp.max}
minTemp={this.state.weatherData.list[1].temp.min}
gifDescription=
{this.state.weatherData.list[1].weather[1].description}
/>
</div>
)
}
Hopefully, this solves the use case.

Categories

Resources