I am working on building pagination. I'm still working on my API to fetch posts based on pagination, but at the moment I am stuck on a problem with states.
In my main file (where the posts will be), my code looks like this:
// Import modules
import React from "react";
import axios from "axios";
import url from "url";
// Import components
import { Snippet } from "../Snippet";
import { CreatePost } from "./CreatePost";
import { Pagination } from "./Pagination";
// Import styles
import "./css/Content.css";
// Axios Init
const api = axios.create({
baseURL: `http://localhost:5000/api/`,
});
export class Content extends React.Component {
state = {
collections: [
{ title: "ReactJS", color: "red" },
{ title: "HTML", color: "cyan" },
{ title: "CSS", color: "pink" },
],
snippets: [],
limitSnippets: 3,
page: 0,
};
constructor(props) {
super(props);
}
componentDidMount() {
this.getSnippets();
}
getSnippets = async () => {
try {
let data = await api
.get(
`/snippets/fetchAll?limitSnippets=${this.state.limitSnippets}&page=${this.state.page}`,
{
body: {
limitSnippets: 3,
page: 1,
},
}
)
.then(({ data }) => data);
this.setState({ snippets: data });
} catch (error) {
console.log(error);
}
};
updatePagination = (page) => {
this.state.page = page;
console.log(this.state.page);
};
render() {
return (
<div className="content">
<h1 className="content-header">Snippets</h1>
<CreatePost contentUpdater={this.getSnippets} />
<Pagination updatePagination={this.updatePagination} />
<div className="w-layout-grid grid">
{this.state.snippets.map((snippet, i) => {
return (
<Snippet
key={i}
title={snippet.title}
code={snippet.code}
updatedDate={snippet.updatedDate}
createdDate={snippet.createdDate}
language={snippet.language}
creator={snippet.creator}
collections={snippet.collections}
/>
);
})}
</div>
<Pagination />
</div>
);
}
}
export default Content;
In pagination file, my code looks like this:
export const Pagination = (props) => {
// States
const [page, setPage] = useState(0);
// Axios Init
const api = axios.create({
baseURL: `http://localhost:5000/api/`,
});
const handleLeft = (event) => {
event.preventDefault();
if (page > 0) {
setPage(page - 1);
props.updatePagination(page);
} else {
console.log("handleLeft(): page not > 0");
}
//props.updatePagination(page);
//}
};
const handleRight = (event) => {
event.preventDefault();
// page < fetchAllPages
setPage(page 1);
props.updatePagination(page);
};
/*useEffect(() => {
props.updatePagination(page);
}, [page]);
*/
return (
<div className="paginate-div">
<div className="paginate-next">
<div className="paginate-next-icon" onClick={handleLeft}>
<i className="fas fa-caret-left"></i>
</div>
</div>
<a href="#" className="paginate-button first w-inline-block">
<div className="paginate-text">1</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">2</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">3</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">4</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">5</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">6</div>
</a>
<a href="#" className="paginate-button last w-inline-block">
<div className="paginate-text">...</div>
</a>
<div className="paginate-next" onClick={handleRight}>
<div className="paginate-next-icon">
<i className="fas fa-caret-right"></i>
</div>
</div>
</div>
);
};
I have my pagination component which is passed a prop thats a function to updatePagination(). The pagination component has functions for left and right button for switching thru pagination, and when it is clicked, the main file gets the pagination updated.
The problem I am having (SORRY IF CONFUSING BY THE WAY I WORDED THIS) The default page is 0 (which is basically page 1). The crazy thing is when I press right (handleRight is called on submit), it stays at page 0, then if I click it again it goes to 1, then after if I press the left button (which called handleLeft on submit) while it is on page 1, it goes up to 2 somehow, but if I click it again it goes back down to 1, then if I click again it goes to 0.
Why is this strange problem occuring?? Please help me ASAP
CodePudding user response:
setPage
is asynchronous, so when you setPage
to decrement and then immediately call props.updatePage
, props.updatePage
is receiving the old value of page
. You can read all about this common problem here.
const handleRight = (event) => {
event.preventDefault();
// Hey React, set page to page 1 and rerender the component
setPage(page 1);
// But before you do that, call props.updatePagination on the old value
props.updatePagination(page);
};
You should ask yourself, though, why you even store two stateful values of page
at all (one in the parent component, one in the child component). Couldn't the Content
component keep track of your page state (as it already does) and pass it down as a prop, getting rid of your need for a page state in the child component? This is called Lifting State Up, and it's a fundamental concept in React, which incentivizes you to use the least amount of state possible to avoid exactly this kind of desync. Furthermore, from the code you've shared, the Pagination
component just displays the page numbers - why does it even need to be stateful at all?
CodePudding user response:
The problem was that the old value of page was being used in updatePagination(), I fixed this by not running updatePagination(page) in the same place, I used useEffect(), and checked for changes to {page}, and ran updatePagination in there.
useEffect(() => {
props.updatePagination(page);
}, [page]);
The handleLeft, handleRight functions were changed to look like this:
const handleLeft = (event) => {
event.preventDefault();
let newPage = page - 1;
if (page > 0) {
setPage(newPage);
} else {
console.log("handleLeft(): page not > 0");
}
//}
};
NOTE" In the comments section, someone made a point that I should not be storing the page number in two places, but rather store them in one place and pass it over as props. I have not currently tried to do this, but I plan on doing this soon. But for now, this is the best answer for this scenario.