Having trouble implementing pagination into my code using axios - javascript

I am a beginner to REACT and I am trying to figure out how to implement pagination into my React project using axios. The api I am using (swapi.dev) has a total of 87 characters, when you submit a GET request with https://swapi.dev/api/people you only get 10 characters per page, but to can use the link in postman, which says "next" to switch between each pages of characters. I am trying to make an request with axios to paginate each list of 10. I figured a for loop would be good to use but I am not sure.
import React, { useEffect, useState } from "react";
import axios from "axios";
import _ from "lodash";
import ReactPaginate from "react-paginate";
import Table from "react-bootstrap/Table";
export default function Posts() {
const [characterData, setCharacterData] = useState([]);
//use a for loop to loop through all sites
useEffect(() => {
axios
.get("https://swapi.dev/api/people/")
.then((res) => {
console.log(res.data.next);
setCharacterData(res.data.results);
})
.catch((err) => {
console.log(err);
});
}, []);
// for ( i = 0; i < data.results.length, i++) {
// names = names + data.results[i].name
// }
// function handleclick(e) => {
// }
return (
<>
<Table stripped bordered hover className="project--table">
<thead>
<tr>
<th>Name</th>
<th>Birth Year</th>
<th>Height</th>
<th>Mass</th>
<th>Homeworld</th>
<th>Species</th>
</tr>
</thead>
<tbody>
{characterData.map((character, index) => {
return (
<tr key={index}>
<td>{character.name}</td>
<td>{character.birth_year}</td>
<td>{character.height}</td>
<td>{character.mass}</td>
<td>{character.homeworld}</td>
<td>{character.species}</td>
</tr>
);
})}
</tbody>
</Table>
{/* <button onClick={}>Next</button> */}
</>
);

I checked your api and the next key have a value that contains the url for the next page. No need for a loop you can just check if the next key exists and depending on that you send a request.
you can create a new state that will contain the next uri and on the onClick function you check if for the "next" key.
it's something like this :
const [next,setNext]= useState("");
in the useEffect you wrote you can add :
axios
.get("https://swapi.dev/api/people/")
.then((res) => {
console.log(res.data.next);
setCharacterData(res.data.results);
if (res.data.next) setNext(res.data.next);
})
and finally in the onClick function :
if(next) {
axios.get(next).then(res=> setCharacterData(res.data.results))
}
I think there is a better solution for this but you can work with this atm.

Related

react fetching and mapping data

import React,{useState, useEffect} from 'react'
import { useParams } from 'react-router-dom'
import Home from './Home'
import './detailpage.css'
function DetailPage({name,
info,
genre,
_id,
episodeNumber,
poster}) {
const [shows, setShows]= useState([{name:'',
info:'',
airingDate:'',
_id:'',
genre:'',
episodeNumber:'',
poster:''
}])
const params= useParams();
useEffect(()=>{
fetch("/home")
.then(res => res.json())
.then(jsonRes => setShows(jsonRes))
}, [])
const b = JSON.stringify(params);
const newShows = shows.filter(a=>a._id===b)
console.log(newShows)
return (
<div>
<h2>.</h2>
<h2>.</h2>
<h2>.</h2>
<h2>{JSON.stringify(params)}</h2>
<h2>{shows.genre}</h2>
{newShows.map(a=>
<div>
<div className='container'>
<img className='showImg' src={a.poster} alt=''></img>
<h2 className='showTitle'>{a.title}</h2>
<h3>{a.genre}</h3>
<p className='showInfo'>{a.info} </p>
</div>
</div>
)}
<h2>{episodeNumber}</h2>
<h2>{shows.info}</h2>
</div>
)
}
export default DetailPage
I have tv shows on my Home page and after clicking the image I want it to load the detail page about the clicked show however I couldn't manage to do it. I tried 'filter' method in the code but it didn't work I also tried like this
const newShows = shows.filter(a=>a.genre.length>5)
it works but this is not what I want. I would be really happy if someone could've helped. Thank you so much.
If I were you, I wouldn't use this fetch, as when you click on the image from your home you already know which tv show you want to display more details about.
I would use something like useLocation from react-router-dom, and while changing pages (home -> detail page about a tv show) carry a state variable with the specific tv show details.
https://v5.reactrouter.com/web/api/Hooks/usehistory
const handleClick = (state) => {
history.push({ pathname: "/detail-page", state })
}
<YourTvShowImage onClick={() => handleClick(TvShowData)} />
Then on your detail page class you use something like
https://v5.reactrouter.com/web/api/Hooks/uselocation
const location = useLocation()
const [tvShowData, setTvShowData] = useState()
useEffect(() => {
if (location.state) {
setTvShowData(location.state)
}
}, [location])

Is it possible to place useEffect inside a if statement?

So guys, I've been working on my Landing and Dashboard page.
So workflow of the page is this:
User gets on Landing page where he can choose to insert into form location, or press a button to recive all locations. Now on the backend I've made two APIs one to get all locations, and second where I've added :location as req.body.param and then filter locations based on that param. And everything works fine in postman.
Now because I've got two ways of user getting locations(all of them or some that he wants) I've thinked that I place two useEffects inside if statement like this:
const filter = props.location.data;
if (filter) {
useEffect(() => {
const fetchFiltered = async () => {
const res = await ArticleService.filterByName(filter);
setContent(res.data);
};
fetchFiltered();
}, []);
} else {
useEffect(() => {
const fetchPosts = async () => {
const res = await ArticleService.articles();
setContent(res.data);
};
fetchPosts();
}, []);
}
So my logic behind this was if there is filter inside props.location execute me useEffect which gets data from ArticleService who then send filter inside of a api url. If there is no filter just retrieve me all data, and setContent to res.data.
But when I compiled the code error is this: React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render
Is there some way of doing this with my logic or I need to create two components: one normal dashboard and second for filtered result?
Landing.js where user sends location
<Form>
<div className='form-group'>
<Input
type='text'
className='form-control text-center'
name='name'
placeholder='Enter desired location'
value={location}
onChange={onChangeLocation}
/>
<Link to={{ pathname: '/dashboard', data: location }}>
<i className='fas fa-check'></i>
</Link>
</div>
<p className='text-center'>or</p>
<Link className='btn btn-primary btn-block' to='/dashboard'>
Show all locations
</Link>
</Form>
Dashboard.js
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import Pagination from 'react-js-pagination';
import ArticleService from '../../services/article.service';
const Dashboard = (props) => {
const [content, setContent] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage] = useState(10);
const filter = props.location.data;
if (filter) {
useEffect(() => {
const fetchFiltered = async () => {
const res = await ArticleService.filterByName(filter);
setContent(res.data);
};
fetchFiltered();
}, []);
} else {
useEffect(() => {
const fetchPosts = async () => {
const res = await ArticleService.articles();
setContent(res.data);
};
fetchPosts();
}, []);
}
let counter = content.length;
// Get current posts
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = content.slice(indexOfFirstPost, indexOfLastPost);
// Change page
const handlePageChange = (pageNumber) => {
setCurrentPage(pageNumber);
};
const render = (item, index) => {
return (
<tr key={index}>
<td className='text-center'>
<div key={item.id}>
<img
src={`${item.pictures}`}
alt='slika artikla'
className='rounded'
></img>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<h4>{item.descr}</h4>
<br></br>
<h6 className='text-left'>Number of m2: {item.sqm}m2</h6>
<div className='text-left'>
<small className='text-left'>
{' '}
<a href={item.link} target='_blank' rel='noopener noreferrer'>
Show on website
</a>
</small>
</div>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<h4>{item.price}</h4>
<small className='text-left'>Price per m2: {item.ppm2}</small>
</div>
</td>
<td className='text-center'>
<div key={item.id}>
<Link to={`/article/${item.id}`}>
<h4>Show</h4>
</Link>
</div>
</td>
</tr>
);
};
return (
<div>
<div className='container'>
<h4 className='text-center'>
Number {counter}
</h4>
<div className='table-responsive'>
<table className='table'>
<thead className='thead-dark'>
<tr>
<th className='text-center' scope='col'>
Pic
</th>
<th className='text-center' scope='col'>
Description
</th>
<th className='text-center w-25' scope='col'>
Price
</th>
<th className='text-center' scope='col'>
Show offer
</th>
</tr>
</thead>
<tbody>{currentPosts.map(render)}</tbody>
</table>
</div>
</div>
<nav>
<div className='w3-bar w3-xlarge'>
<ul className='pagination justify-content-center'>
<li className='page-item'>
<Pagination
hideDisabled
hideNavigation
hideFirstLastPages
currentPage={currentPage}
itemsCountPerPage={10}
totalItemsCount={content.length}
pageRangeDisplayed={indexOfLastPost}
onChange={handlePageChange}
/>
</li>
</ul>
</div>
</nav>
</div>
);
};
export default Dashboard;
Thanks! :D
Basic answer, no, you cannot conditionally call useEffect. You must put the conditional logic inside the useEffect callback.
const filter = props.location.data
useEffect(() => {
if (filter) {
const fetchFiltered = async () => {
const res = await ArticleService.filterByName(filter)
setContent(res.data)
}
fetchFiltered()
} else {
const fetchPosts = async () => {
const res = await ArticleService.articles()
setContent(res.data)
}
fetchPosts()
}
}, [filter, setContent, ArticleService.filterByName, ArticleService.articles])
Hooks in React do not really follow the standard rules of javascript. There are performance reasons around the way they have to be implemented, often some caching is done to stop excess code being executed every time a render pass is done.
The useEffect hook will only run its callback function during a render where one of the values in the dependency array (the second arg of useEffect) has changed. It's standard to put in all external values that could change. Thus when the value of filter changes, the app will rerender, the useEffect will do a comparision, realise that something has changed and run it's call back again which will then perform the if statement.
You can read more about this in the performance part of the docs
In addition to the above answer,from the official documentatation
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.

Why this error arising? users.map is not a function

I am trying to render names from a API with Axios. Even I can see all data in console but unable to render as it shows error:
users.map is not a function
Below I'm sharing my code of the file. I'm quite new that might be the reason I'm unable to figure it out.
import React from 'react';
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css'
import { Container, Table} from "react-bootstrap";
import axios from 'axios';
class App extends React.Component {
state = {
users: [],
};
componentDidMount () {
axios.get('https://5w05g4ddb1.execute-api.ap-south-1.amazonaws.com/dev/profile/listAll')
.then(response => {
const users = response.data;
this.setState({ users });
console.log(this.state.users);
})
}
render() {
const { users } = this.state;
return (
<div className="App">
<Container fluid>
<Table striped bordered hover size="sm">
<thead>
<tr>
<th><div id="">Image</div></th>
<th>Name</th>
<th>Gender</th>
<th>Age</th>
<th>Date</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
{ users.map(user => { return <td key={user.id}>{ user.name }</td> }) }
</tr>
</tbody>
</Table>
</Container>
</div>
)
}
}
export default App;
The response you are getting is not an array, but an object like this:
{"list":[{"id":"MnJJA0dbuw","name":"Anand Sharma","img":"https://incablet-tests.s3.ap-south-1.amazonaws.com/conference-content/photos/sponsors/Anand.jpeg","gender":"m","age":46,"date":"23/11/2019","status":"onboarded"}]}
You can access the array by replacing const users = response.data; with const users = response.data.list;
In your axios get, in the “then” part change the line:
const users = response.data;
To:
const users = response.data.list;
fast hack
{users.list.map(user => { return <td key={user.id}>{ user.name }</td> }) }
There are 2 errors:
the response from api is not a list but an object, u need to go to response.list to use the list from the response
it should be
const users = response.data.list
your
this.setState({users})
,you will need to change it to
this.setState({users:<VARIABLE_NAME_TO_HOLD_USER_DATA>})
even what you write is valid like in ES6, that is somehow a bad idea, it is not a clear what are you doing, just define what u want to copy to ur component state

React problem with getting image from API

I am currently practicing React, and my goal is to build a simple app that searches movies and display some short info about them as results. I managed to pull data from API and store em in React hooks. I can access any data, but when I try to pull images I get error:
TypeError: Cannot read property 'medium' of null.
Here are the API results:
http://api.tvmaze.com/search/shows?q=$girls
I find an image that I want to use stored in {show.image.medium}
Here is my React code:
import React, {useState, useEffect} from 'react';
import Movie from './Movie';
const App = () => {
const [movies, setMovies] = useState([]);
useEffect(() => {
getMovies();
}, []);
const getMovies = async () => {
const response = await fetch(`http://api.tvmaze.com/search/shows?q=$girls`);
const data = await response.json();
setMovies(data);
console.log(data)
;}
return (
<div>
<form className='search-form'>
<input type='text' className='search-bar' placeholder='search movie'>
</input>
<button type='submit' className='search-button'>
Search
</button>
</form>
{movies.map(movie => (
<Movie title={movie.show.name} image={movie.show.image.medium} />
))}
</div>
);
};
export default App;
and Movie.js file:
import React from 'react';
const Movie = ({title, image}) => {
return(
<div>
<h1>{title}</h1>
<img src={image} alt=''/>
</div>
);
}
export default Movie;
so I basically mapped the results in movie array, but {movie.show.image.medium} just won't work, while pulling any other data work just fine.
I know that this is probably an easy fix, but I tried everything and searched for an answer for hours and still, nothing worked. I would really appreciate it if someone can explain to me what I am doing wrong. Thanks in advance!
In the API call there is one value where movie.show.image is technically null. For null you could not get any properties, even medium.
What you can do as a solution is the following:
{
movies.map(movie =>
movie.show.image ?
<Movie title={movie.show.name} image={movie.show.image.medium} /> :
null)
}
Additionally you need to return from Array.prototype.map().
Iteration from the API on my console:
I hope that helps!

How to Get a specific question by Id in React

I have a question concerning Mern stack. I have a program which is similar to stackOverflow, where you post a question and someone can reply. At the moment my program is able to post questions and also get a list of all questions. I have a link at each and every questions,so that when you click at any of the questions it should open that specific question. The id of the questions is visible at the rout e.g http://localhost:3000/link/5cae2dda9723ad157c085370. The problem am having is to get the content of that specific question
//*this code is able get the list of all questions*//
import React, { Component } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import { EMLINK } from "constants";
const Question = props => (
<tr>
<td>{props.question.author_name}</td>
<td>{props.question.question_title}</td>
<td>{props.question.question_input}</td>
<td>
<Link to={"/link/" + props.question._id}>comment</Link>
</td>
</tr>
);
class QuestionList extends Component {
constructor(props) {
super(props);
this.state = { questions: [] };
}
componentDidMount() {
axios
.get("http://localhost:4000/questions/")
.then(response => {
this.setState({ questions: response.data });
})
.catch(function(error) {
console.log(error);
});
}
questionList() {
return this.state.questions.map(function(currentQuestion, i) {
return <Question question={currentQuestion} key={i} />;
});
}
render() {
return (
<div>
<h3>Question List</h3>
<table className="table table-striped" style={{ marginTop: 20 }}>
<thead>
<tr>
<th>Name</th>
<th>Title</th>
<th>Question</th>
<th>Action</th>
</tr>
</thead>
<tbody>{this.questionList()}</tbody>
</table>
</div>
);
}
}
export default QuestionList;
*/The below code is the one that i need to only show one specific question by ID*//
import React, { Component } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
const Questioners = props => (
<tr>
<td>{props.question.author_name}</td>
<td>{props.question.question_title}</td>
<td>{props.question.question_input}</td>
</tr>
);
class QuestionLink extends Component {
constructor(props) {
super(props);
this.state = {
author_name: "",
question_title: "",
question_input: ""
};
}
render() {
return (
<div>
<h3>Question List</h3>
<table className="table table-striped" style={{ marginTop: 20 }}>
<thead>
<tr>
<th>Name</th>
<th>Title</th>
<th>Question</th>
</tr>
</thead>
<tbody>{}</tbody>
</table>
</div>
);
}
}
export default QuestionLink;
I've done the following in these scenarios:
Take the ID as a parameter to the Component (in this case, QuestionLink)
Retrieve the question from your REST API as a get for the particular resource (with ID) in your ComponentDidMount
When mounting your react app (top-level component), retrieve the Id from the url. I prefer to use a query string
import { parse } from "querystring";
let values = parse(window.location.search.substring(1));
And then mount <QuestionLink questionId={values["questionId"]} />
EDIT: I haven't used template engines for this, but it should be well suited for this kind of work. You can use something like pug for the server side rendering, pass the id to the view from your middleware, and render to a react component. I'd probably only do this if I did this sort of processing extensively and/or needed information that only the server had.
Thanks for help, i made it work. i solved the issue as follow
<Route
path="/link/:id"
render={props => <QuestionLink {...props} />}
/>
this.state = {
currentQuestion: {}
};
componentDidMount() {
axios
.get("http://localhost:4000/questions/")
.then(response => {
this.setState({
currentQuestion: response.data.find(elm =>
elm._id == this.props.match.params.id
)
});
})

Categories

Resources