The problem I'm having is that the initial state is never updated on BasePagination even though it's clearly being re-rendered and the parent component is successfully changing the page and setting the new state.. But it's not getting through to the child component even though it's explicitly setting in JSX attributes in render method.
Parent component:
function Users(props) {
const [currentPage, setCurrentPage] = useState(1);
const [perPage, setPerPage] = useState(1);
const [sort, setSort] = useState('id');
const [dir, setDir] = useState('desc');
const [data, setData] = useState({results: []});
const [totalPages, setTotalPages] = useState(1);
useEffect(() => {
const fetchData = async (page, sort, dir, perPage) => {
const users = await get(url.GET_USERS '?page=' page '&sort=' sort '&dir=' dir '&per=' perPage);
setData(users);
setTotalPages(Math.ceil(users.total / perPage));
}
fetchData(currentPage, sort, dir, perPage);
return () => {
// this now gets called when the component unmounts
};
}, [currentPage]);
const handleClick = (e, index) => {
e.preventDefault();
this.setState({
currentPage: index
});
};
return (
<React.Fragment>
<div className="page-content">
<div className="container-fluid">
<Row>
<Col>
<Card>
<CardBody>
<CardTitle className="h4">Users</CardTitle>
<p className="card-title-desc">
in the api database
</p>
<div className="table-rep-plugin">
<div
className="table-responsive mb-0"
data-pattern="priority-columns"
>
<Table
id="tech-companies-1"
className="table table-striped table-bordered"
>
<Thead>
<Tr>
<Th data-priority="1">ID</Th>
<Th data-priority="2">Name</Th>
<Th data-priority="3">Email</Th>
</Tr>
</Thead>
<Tbody>
{data.results.map((item) => {
return (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.email}</td>
</tr>
);
})}
</Tbody>
</Table>
<BasePagination pages={totalPages} currentPage={currentPage}/>
<Button color="primary" onClick={(currentPage) => setCurrentPage(1)}/> <Button onClick={(currentPage) => setCurrentPage(2)}/>
</div>
</div>
</CardBody>
</Card>
</Col>
</Row>
</div>
</div>
</React.Fragment>
);
}
BasePagination.js
import React from 'react';
import { Pagination, PaginationItem, PaginationLink } from 'reactstrap';
const maxPages = 6;
let pages = [];
export default class BasePagination extends React.Component {
constructor(props){
super(props);
this.state = {count: props.pages, currentPage: props.currentPage};
}
componentDidMount() {
console.log('mount');
}
componentWillUnmount() {
console.log('unmount');
}
arrayRange(start, stop, step, maxPages) {
return Array.from(
{ length: (Math.max(stop, maxPages) - start) / step 1 },
(value, index) => start index * step
);
}
render() {
console.log(this.state);
pages = this.arrayRange(this.state.currentPage, this.state.currentPage maxPages-1, 1, this.state.count);
if (pages > 1) {
return (
<Pagination aria-label="Page navigation example">
<PaginationItem>
<PaginationLink previous href="#" />
</PaginationItem>
{pages.map((x, i) =>
<PaginationItem key={x}>
<PaginationLink href="#">{x}</PaginationLink>
</PaginationItem>
)}
<PaginationItem>
<PaginationLink next href="#" />
</PaginationItem>
</Pagination>
);
} else {
return;
}
}
}
CodePudding user response:
You are copying props into state, which is a common antipattern. It's generally a bad idea to get the props and put them into local component state as you now have 2 copies lying around that can get out of sync unless you write a lot of unnecessary glue code to sync them. Why not just use the props
directly?
This is the reason why the parent updates don't effect the child. You initialize the child with the parent state, and then future state updates in the parent are irrelevant to the child since there's no code to copy updates. But as above, the copy itself is not necessary and an unnecessary barrier.
In BasePagination
, just use the props directly:
import React from 'react';
import { Pagination, PaginationItem, PaginationLink } from 'reactstrap';
const maxPages = 6;
export default class BasePagination extends React.Component {
componentDidMount() {
console.log('mount');
}
componentWillUnmount() {
console.log('unmount');
}
arrayRange(start, stop, step, maxPages) {
return Array.from(
{ length: (Math.max(stop, maxPages) - start) / step 1 },
(value, index) => start index * step
);
}
render() {
let pages = this.arrayRange(this.props.currentPage, this.props.currentPage maxPages-1, 1, this.props.pages);
if (pages > 1) {
return (
<Pagination aria-label="Page navigation example">
<PaginationItem>
<PaginationLink previous href="#" />
</PaginationItem>
{pages.map((x, i) =>
<PaginationItem key={x}>
<PaginationLink href="#">{x}</PaginationLink>
</PaginationItem>
)}
<PaginationItem>
<PaginationLink next href="#" />
</PaginationItem>
</Pagination>
);
} else {
return;
}
}
}
Also, you should always avoid the use of variables outside of the component scope that are mutated like pages
outside your component definition as when these change, you won't get a rerender. You can't use module scope as state. I have removed this. It's not needed anyway.
maxPages
is ok since it is just a constant and not mutated.
CodePudding user response:
So you're having a problem with your BasePagination component not updating even though the parent component is successfully changing the page and setting the new state. It's frustrating when that happens, but don't worry - we can figure it out.
I think the issue might be that you're not passing the updated currentPage state to the BasePagination component. You're using setCurrentPage in the parent component to change the currentPage state, but you're not passing it as a prop to BasePagination.
Try updating your parent component code like this:
<BasePagination pages={totalPages} currentPage={currentPage} />
And in your BasePagination component, you can use the passed in currentPage prop like this:
this.state = {count: props.pages, currentPage: props.currentPage};
This should pass the updated currentPage state from the parent component to the BasePagination component and update it properly.