I am receiving 50 data with get request using axios, and I want to add pagination to my code on every page i want 5 results, how to implement pagination with axios response.
import React from 'react';
import axios from 'axios';
export default class PersonList extends React.Component {
state = {
persons: []
}
componentDidMount() {
axios.get(`https://jsonplaceholder.typicode.com/Todos`)
.then(res => {
const persons = res.data;
this.setState({ persons });
})
}
render() {
return (
<div>
<ul>
{ this.state.persons.map(person => <li>{person.name}</li>)}
</ul>
<button onClick={this.previousPage}>PreviousPage</button>
<button onClick={this.nextPage}>Next Page</button>
</div>
)
}
}
Here is a simple example using library paginate-array to paginate array items stored in the component's state. If you inspect the source for paginate-array, you'd see the logic for creating a "page" is fairly straightforward, so you may be able to use it for inspiration for your own pagination utilities. This example uses Todos from JSONPlaceholder, but you can modify the example as needed. Keep in mind the endpoint https://jsonplaceholder.typicode.com/todos does not have objects with property name as your example suggests. This example does simple checks for page numbers with the click handlers for the previous/next buttons to help ensure invalid pages cannot be selected. This example is assuming you plan to load all the data on the component loading, rather than requesting new pages of data via axios/fetch.
import React, { Component } from 'react';
import paginate from 'paginate-array';
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
todos: [],
size: 5,
page: 1,
currPage: null
}
this.previousPage = this.previousPage.bind(this);
this.nextPage = this.nextPage.bind(this);
}
componentDidMount() {
fetch(`https://jsonplaceholder.typicode.com/todos`)
.then(response => response.json())
.then(todos => {
const { page, size } = this.state;
const currPage = paginate(todos, page, size);
this.setState({
...this.state,
todos,
currPage
});
});
}
previousPage() {
const { currPage, page, size, todos } = this.state;
if (page > 1) {
const newPage = page - 1;
const newCurrPage = paginate(todos, newPage, size);
this.setState({
...this.state,
page: newPage,
currPage: newCurrPage
});
}
}
nextPage() {
const { currPage, page, size, todos } = this.state;
if (page < currPage.totalPages) {
const newPage = page + 1;
const newCurrPage = paginate(todos, newPage, size);
this.setState({ ...this.state, page: newPage, currPage: newCurrPage });
}
}
render() {
const { page, size, currPage } = this.state;
return (
<div>
<div>page: {page}</div>
<div>size: {size}</div>
{currPage &&
<ul>
{currPage.data.map(todo => <li key={todo.id}>{todo.title}</li>)}
</ul>
}
<button onClick={this.previousPage}>Previous Page</button>
<button onClick={this.nextPage}>Next Page</button>
</div>
)
}
}
export default TodoList;
Here is a basic example in action. The example also has an approach to handling a changeable size dropdown.
Hopefully that helps!
Related
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;
I use MobX to control my ReactJS state/components and I also use an async call through MobX in order to retrieve the data, this is typically called in my header throuhg componentDidMount().
(I already know my code isn't the cleanest and most likely has errors, cut me some slack as I'm learning/coding this completely on my own at this point with no educational background in programming)
import React from 'react';
import { Layout, Row, Col, Menu, Avatar, Tag } from 'antd';
import { inject, observer } from 'mobx-react';
import Icon from '#ant-design/icons';
const { Header } = Layout;
const { SubMenu } = Menu;
#inject('store')
#observer
class PageHeader extends React.Component {
componentDidMount() {
this.props.store.getOrders();
}
Say for instance I was not in the Application, but I was still logged in through my LocalStorage data, and I went to a page "http://localhost/orders/123456". 123456 would be my order ID and this page would display it's details. Now considering I was not on the page, the DOM wasn't rendered, right? Right... But I'm still logged in through my LocalStorage, so when I visit the page - it's rendering blank because MobX has to wait for the API call to retrieve the data. I need to be able to pull this data and make sure it's rendered on the page, so I some how need the API to be retrieve before rendering to ensure it's pull the data out of MobX with the specified OrderID, in this case 123456.
Below is two ways I've made my componentDidMount
#inject('store')
#observer
class LoadPage extends React.Component {
state = {
visible: false,
ordernum: this.props.match.params.id,
orderkey: null,
}
componentDidMount() {
document.title = this.props.match.params.id;
route_ordernum = this.props.match.params.id;
if (this.props.store.orders.length === 0) {
fetch('http://localhost:5000')
.then(res1 => res1.json())
.then(data => this.props.store.setOrders(data))
.then(this.setState({
orderkey: this.props.store.orders.filter(order => order._id.includes(route_ordernum)).map((data, key) => { return data.key })
}))
}
if (this.props.store.orders.length > 0) {
this.setState({
orderkey: this.props.store.orders.filter(order => order._id.includes(route_ordernum)).map((data, key) => { return data.key })
})
}
console.log(this.state.orderkey)
}
render() {
Example #2
componentDidMount() {
document.title = this.props.match.params.id;
route_ordernum = this.props.match.params.id;
if (this.props.store.orders.length === 0) {
this.props.store.getOrders().then(dataloads => {
this.setState({
orderkey: this.props.store.orders.filter(order => order._id.includes(route_ordernum)).map((data, key) => { return data.key })
})
})
}
if (this.props.store.orders.length > 0) {
this.setState({
orderkey: this.props.store.orders.filter(order => order._id.includes(route_ordernum)).map((data, key) => { return data.key })
})
}
console.log(this.state.orderkey)
}
I'm just passing through my MobX and using two separate classes to create my state now.
#inject('store')
#observer
class LoadMain extends React.Component {
render() {
return(
this.props.store.orders.length === 0 ? <Content style={{ backgroundColor: "#ffffff" }}><center><Spinner /></center></Content> : <OrderPage ordernum={this.props.match.params.id} />
);
}
}
I am new to MST and is having a hard time finding more examples with async actions. I have an api that will return different data depending on the params you pass to it. In this case, the api can either return an array of photos or tutorials. I have set up my initial values for the store like so:
data: {
photos: [],
tutorials: []
}
Currently, I am using applySnapshot to update the store and eventually, that will trigger a re-render of my React component. In order to display both photos and tutorials, I need to call the api twice (Once with the params for photos and the second time for tutorials). I am running into an issue where the snapshot from the first update shows that photos and tutorials have the same values and only on the second update, do I get the correct values. I am probably misusing applySnapshot to re-render my React components. I would like to know the better/proper way of doing this. What is the best way to re-render my React components after the api has yielded a repsonse. Any suggestions are much appreciated
I have set up my store like this:
import { RootModel } from '.';
import { onSnapshot, getSnapshot, applySnapshot } from 'mobx-state-tree';
export const setupRootStore = () => {
const rootTree = RootModel.create({
data: {
photos: [],
tutorials: []
}
});
// on snapshot listener
onSnapshot(rootTree, snapshot => console.log('snapshot: ', snapshot));
return { rootTree };
};
I have created the following model with an async action using generators:
import {types,Instance,applySnapshot,flow,onSnapshot} from 'mobx-state-tree';
const TestModel = types
.model('Test', {
photos: types.array(Results),
tutorials: types.array(Results)
})
.actions(self => ({
fetchData: flow(function* fetchData(param) {
const results = yield api.fetch(param);
applySnapshot(self, {
...self,
photos: [... results, ...self.photos],
tutorials: [... results, ...self.tutorials]
});
})
}))
.views(self => ({
getPhoto() {
return self.photos;
},
getTutorials() {
return self.tutorials;
}
}));
const RootModel = types.model('Root', {
data: TestModel
});
export { RootModel };
export type Root = Instance<typeof RootModel>;
export type Test = Instance<typeof TestModel>;
React component for Photos.tsx
import React, { Component } from 'react';
import Spinner from 'components/Spinner';
import { Root } from '../../stores';
import { observer, inject } from 'mobx-react';
interface Props {
rootTree?: Root
}
#inject('rootTree')
#observer
class Photos extends Component<Props> {
componentDidMount() {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.data.fetchData('photo');
}
componentDidUpdate(prevProps) {
if (prevProps.ctx !== this.props.ctx) {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.data.fetchData('photo');
}
}
displayPhoto() {
const { rootTree } = this.props;
if (!rootTree) return null;
// calling method in MST view
const photoResults = rootTree.data.getPhoto();
if (photoResults.$treenode.snapshot[0]) {
return (
<div>
<div className='photo-title'>{'Photo'}</div>
{photoResults.$treenode.snapshot.map(Item => (
<a href={photoItem.attributes.openUrl} target='_blank'>
<img src={photoItem.url} />
</a>
))}
</div>
);
} else {
return <Spinner />;
}
}
render() {
return <div className='photo-module'>{this.displayPhoto()}</div>;
}
}
export default Photos;
Similarly, Tutorials.tsx is like so:
import React, { Component } from 'react';
import Spinner from '';
import { Root } from '../../stores';
import { observer, inject } from 'mobx-react';
interface Props {
rootTree?: Root;
}
#inject('rootTree')
#observer
class Tutorials extends Component<Props> {
componentDidMount() {
if (this.props.ctx) {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.data.fetchData('tuts');
}
}
componentDidUpdate(prevProps) {
if (prevProps.ctx !== this.props.ctx) {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.search.fetchData('tuts');
}
}
displayTutorials() {
const { rootTree } = this.props;
if (!rootTree) return null;
// calling method in MST view
const tutResults = rootTree.data.getTutorials();
if (tutResults.$treenode.snapshot[0]) {
return (
<div>
<div className='tutorials-title'>{'Tutorials'}</div>
{tutResults.$treenode.snapshot.map(tutorialItem => (
<a href={tutorialItem.attributes.openUrl} target='_blank'>
<img src={tutorialItem.url} />
</a>
))}
</div>
);
} else {
return <Spinner />;
}
}
render() {
return <div className='tutorials-module'>{this.displayTutorials()}</div>;
}
}
export default Tutorials;
Why are you using applySnapshot at all in this case? I don't think it's necessary. Just assign your data as needed in your action:
.actions(self => ({
//If you're fetching both at the same time
fetchData: flow(function* fetchData(param) {
const results = yield api.fetch(param);
//you need cast() if using Typescript otherwise I think it's optional
self.photos = cast([...results.photos, ...self.photos])
//do you really intend to prepend the results to the existing array or do you want to overwrite it with the sever response?
self.tutorials = cast(results.tutorials)
})
}))
Or if you need to make two separate requests to fetch your data it's probably best to make it two different actions
.actions(self => ({
fetchPhotos: flow(function* fetchPhotos(param) {
const results = yield api.fetch(param)
self.photos = cast([... results, ...self.photos])
}),
fetchTutorials: flow(function* fetchTutorials(param) {
const results = yield api.fetch(param)
self.tutorials = cast([... results, ...self.tutorials])
}),
}))
Regardless, it doesn't seem like you need applySnapshot. Just assign your data in your actions as necessary. There's nothing special about assigning data in an async action.
I'm writing a reaction web application with firebase backend. Based on field query of one document, I wanted to access another document.
I'm only able to return the query and directly use it for accessing the document. It's making a call to the function and query over and over again.
import React, { Component } from 'react';
import { compose } from 'recompose';
import { withAuthorization } from '../Session';
import { withStyles } from '#material-ui/core/styles';
import Paper from '#material-ui/core/Paper';
import './Image/1.jpeg'
import Album from './Album.js';
const styles = theme => ({
root: {
...theme.mixins.gutters(),
paddingTop: theme.spacing.unit * 2,
paddingBottom: theme.spacing.unit * 2,
},
});
class ChildPage extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
items: [],
};
this.classes = props;
}
componentDidMount() {
this.setState({ loading: true });
let query = [];
this.unsub = this.props.firebase.users().doc(this.props.firebase.userId()).get().then(doc => {
query.push(doc.data().LinkedUsername)
const lU = this.props.firebase.users().where("email", "==", query[0])
lU.get().then(snapshot => {
console.log(snapshot.docs[0].id)
})
})
this.unsubscribe = this.props.firebase
.users().doc(this.props.firebase.userId()).collection('tasks')
.onSnapshot(snapshot => {
let items = [];
snapshot.forEach(doc =>
(doc.data().status === false) ?
items.push({ ...doc.data(), uid: doc.id })
:
null
);
this.setState({
items,
loading: false,
});
});
}
componentWillUnmount() {
this.unsubscribe();
this.unsub();
}
removeItem(itemId) {
const itemRef = this.props.firebase.users().doc(`${itemId}`);
itemRef.remove();
}
render() {
return (
<div className='background'>
<div className='topBar'>
</div>
<Paper className={ this.classes.root } elevation={ 1 }>
<Album cards={ this.state.items } />
</Paper>
</div>
);
}
}
const condition = authUser => !!authUser;
export default compose(
withAuthorization(condition),
withStyles(styles),
)(ChildPage)
I want the query to run one time and assign the return value to a variable. Then be able to use that value to access them and load the documents.
Currently, this gives me a document id in the console. And if I want to use that id, I take the next two "})" brackets and put them before
"
}
componentWillUnmount() {"
and take everything inside the console i.e. "snapshot.docs[0].id" and put it inplace of "this.props.firebase.userId()" in the doc of this.unsubscribe.
But it's what calls the function over and over and gives the error,
"Warning: Encountered two children with the same key, [object Object]. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version."
I am trying to create a function that when clicking the “next” button pass to another random object of my api. But I'm not able to do the function retrieve the other values and update the information in the browser.
App.js
import React, { Component } from 'react';
import './App.css';
class App extends Component {
state = {
data: [],
chosenPlanet: 0,
}
componentDidMount() {
const url = 'http://localhost:4000/results'
fetch(url)
.then(response => response.json())
.then(response => {
this.setState({
data: response.results,
})
})
}
renderPlanet = (event) => {
const planetRandom = Math.floor(Math.random() * this.state.data)
return planetRandom
}
render() {
const index = Math.floor(Math.random() * this.state.data.length)
const planet = this.state.data[index]
console.log(planet)
return (
<div className="App">
<div>{this.planet.name}</div>
<button onClick={this.renderPlanet}>Next</button>
</div>
)
}
}
export default App;
In event handlers, state must be updated. State updates causes component to update itself or re-render. In this case, since no state is changed, and only an update needs to be triggered, a forceUpdate can be used.
handleNext = (event) => {
this.forceUpdate();
}