I have a react app which is the front end for a shopping site.
I have a products page and I am trying to add pagination from react-js-pagination to the bottom of it so that I do not have to render the entire list of products at once.
I have followed the guidlines on implementing the pagination from https://www.npmjs.com/package/react-js-pagination but I still cannot get it to display (the rest of the page displays properly).
Can anybody see why?
Please see my code for the entire page below:
import React from 'react';
import Pagination from 'react-js-pagination';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import changeBrandFilter from '../actions/changeBrandFilter';
import changePriceFilter from '../actions/changePriceFilter';
import CategoryOverview from './CategoryOverview';
import Filter from './Filter';
import ProductsListItem from './ProductsListItem';
import ProductsPageContainerCSS from './ProductsPageContainer.css';
class ProductsPage extends React.Component{
createCategoryOverview() {
return this.props.overview.map(overview => {
return (
<CategoryOverview
title={overview.title}
text={overview.text}
image={overview.imageSource}
alt={overview.imageAlt}
/>
)
})
}
createBrandFilterList() {
return this.props.brandFilters.map(filter => {
return (
<Filter
key={filter.brand}
id={filter.brand}
changeFilter={() => this.props.changeBrandFilter(filter)}
inuse={filter.inuse}
disabled={filter.disabled}
/>
)
})
}
createPriceRangeFilterList() {
return this.props.priceRangeFilters.map(filter => {
return (
<Filter
key={filter.priceRange}
id={filter.priceRange}
changeFilter={() => this.props.changePriceFilter(filter)}
inuse={filter.inuse}
disabled={filter.disabled}
/>
)
})
}
filterDivExtenionToggle () {
var filterDivExtension = document.querySelector('.filterDivExtension');
var chevronUp = document.querySelector('#chevronUp');
var chevronDown = document.querySelector('#chevronDown');
var icon;
if (filterDivExtension.style.display === 'block') {
filterDivExtension.style.display = 'none';
chevronUp.style.display = 'none';
chevronDown.style.display = 'block';
} else {
filterDivExtension.style.display = 'block';
chevronUp.style.display = 'block';
chevronDown.style.display = 'none';
}
}
createProductsList() {
if(this.props.products.length > 0) {
return this.props.products.map(product =>{
if (this.props.products.indexOf(product) >= this.state.activePage -1 && this.props.products.indexOf(product) < (this.state.activePage*12)) {
return (
<ProductsListItem
key={product.id}
brand={product.brand}
model={product.model}
price={product.price}
image={product.image}
link={"/"+this.props.match.params.type+"/"+product.id}
/>
)
}
})} else {
return <div>No products match the filter criteria selected above.</div>
}
}
constructor(props) {
super(props);
this.state = {activePage: 1};
}
handlePageChange(pageNumber) {
this.setState({activePage: pageNumber});
}
render () {
return (
<div>
<div className="container">
{this.createCategoryOverview()}
<div ClassName="row">
<div className= "filterDiv col-12">
<div className="iconCrossbar">
<i id="chevronDown" className="fa fa-chevron-down" onClick={this.filterDivExtenionToggle}></i>
<i id="chevronUp" className="fa fa-chevron-up" onClick={this.filterDivExtenionToggle}></i>
</div>
<div className="filterDivExtension">
<div className="row">
<div className="filtersList col-md-6 col-12">
Filter by Brand:
<div>
{this.createBrandFilterList()}
</div>
</div>
<div className="filtersList col-md-6 col-12">
Filter by Price Range:
<div>
{this.createPriceRangeFilterList()}
</div>
</div>
</div>
</div>
</div>
</div>
<div className="row productsList">
{this.createProductsList()}
</div>
</div>
<Pagination
activePage={this.state.activePage}
itemsCountPerPage={12}
totalItemsCount={this.props.products.length}
pageRangeDisplayed={2}
onChange={this.handlePageChange}
/>
</div>
)
}
};
function mapStateToProps(state , ownProps) {
let brandFilters = state.brandFilters;
let filtered_brandFilters = brandFilters;
filtered_brandFilters = filtered_brandFilters.filter(
filter => filter.type === ownProps.match.params.type
)
let priceRangeFilters = state.priceRangeFilters;
let filtered_priceRangeFilters = priceRangeFilters;
filtered_priceRangeFilters = filtered_priceRangeFilters.filter(
filter => filter.type === ownProps.match.params.type
)
let products = state.products;
let overviews = state.overviews;
let overview = overviews.filter(
overview => overview.type === ownProps.match.params.type
)
let filtered_products = products;
filtered_products = filtered_products.filter(
product => product.type === ownProps.match.params.type //gets type from the the route params and finds products which have type that matches
)
let activeBrandFilters = brandFilters.filter(
item => item.inuse === true
);
activeBrandFilters.forEach(filter => {
filtered_products = filtered_products.filter(
product => product.brand === filter.brand
)
});
let activePriceRangeFilters = priceRangeFilters.filter(
item => item.inuse === true
);
activePriceRangeFilters.forEach(filter => {
filtered_products = filtered_products.filter(
product => product.priceRange === filter.priceRange
);
});
return {
overview: overview,
brandFilters: filtered_brandFilters,
priceRangeFilters: filtered_priceRangeFilters,
products: filtered_products
};
};
function matchDispatchToProps(dispatch){
return bindActionCreators({changeBrandFilter: changeBrandFilter, changePriceFilter: changePriceFilter}, dispatch);
};
export const ProductsPageContainer = connect(mapStateToProps, matchDispatchToProps)(ProductsPage);
Any help would be greatly appreciated.
Thanks.
Well, I can't help you with react-js-pagination, on the other hand, I did it very easily using react-prime. Paginator React Prime. Ok, so, I'll try to explain it to you,
first thing is to understand what this framework gives to us:
you import it:
import {Paginator} from 'primereact/components/paginator/Paginator';
then probably, you will have a list of components you have to render in order to paginate through it.
On your container component you have to set these values in order for you paginator to work:
constructor() {
super();
this.state = {first: 0, rows: 10};
this.onPageChange = this.onPageChange.bind(this);
}
onPageChange(event) {
this.setState({
first: event.first,
rows: event.rows
});
}
then you will have the paginator component itself:
<Paginator first={this.state.first} rows={this.state.rows} totalRecords={yourcomponentlist.length} onPageChange={this.onPageChange}></Paginator>
Now let's analyse it, we have a number of rows showing up in each page (rows), and the relative number of the first line to be displayed(first). so, you can have your list of components working with paginator using the slice javascript method to render only the components you wish after paginating.
<tbody>
{
this.props.components.slice(this.state.first, this.state.first +
this.state.rows).map((component) => {
return <ComponentListItem key={component._id} {...componentData} />;
})
}
</tbody>
That's it, I hope I was able to help you understand how this paginator works, react-prime is a great toolbelt, it has many themes for your design as well, I was very happy using it!
Ok so if you read my comments on Leonel's answer you will see that I did manage to get the paginator from react-js-paginator to display but could still not get it to work.
I made my own custom basic paginator component instead.
please find the paginator component that i made below:
import React from 'react';
class Paginaton extends React.Component {
render () {
return (
<div className="row">
<div className="pagination">
<button id="prevPage" className="btn" disabled={this.props.disabled1} onClick={() => this.props.onclick1()}>prev</button>
<button id="nextPage" className="btn" disabled={this.props.disabled2} onClick={() => this.props.onclick2()}>next</button>
</div>
</div>
)
}
}
export default Paginaton;
As you can see this is just a prev button and a next button.
I then, in the container component, made sure that only the button which was required to be active was shown as active and the button that was not required to be active was shown as inactive. this made sure that prev was not a clickable option when on the first page of products and next was not a clickable option when on the last page of products.
I also made sure to add a 'key' prop to the component that was unique to the route that the component was displayed on. This was needed because my pagination relies on a state that I have set in the component which declares the 'activePage' so that when I would go on to a page of products of another type (from kits products to tanks products for example), the component would remount (since both routes render the same component, with the products rendered being decided by the route parameters) and the state would revert to its initial state ({activePage: 1}).
Please see the container component below:
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import changeBrandFilter from '../actions/changeBrandFilter';
import changePriceFilter from '../actions/changePriceFilter';
import CategoryOverview from './CategoryOverview';
import Filter from './Filter';
import ProductsListItem from './ProductsListItem';
import ProductsPageContainerCSS from './ProductsPageContainer.css';
import Pagination from './Pagination';
class ProductsPage extends React.Component{
createCategoryOverview() {
let i = 1;
return this.props.overview.map(overview => {
i++
return (
<CategoryOverview
key={"catOverview"+i} //each child in an array or iterator should have a unique "key" prop
title={overview.title}
text={overview.text}
image={overview.imageSource}
alt={overview.imageAlt}
/>
)
})
}
createBrandFilterList() {
let i = 1;
return this.props.brandFilters.map(filter => {
i++
return (
<Filter
key={filter.brand+i+"brand"}
name={this.props.match.params.type + "brandFilter"} //so that each seperate group of radio buttons (filters) refer only to each other. (the name is shared within each group)
id={filter.brand}
changeFilterResetPageNumber={() => {this.props.changeBrandFilter(filter); this.handlePageChange(1)}} //without page reset would often get no products displayed on filter application due to the activePage state remaining at the page that was active at the time of filter application
inuse={filter.inuse}
/>
)
})
}
createPriceRangeFilterList() {
let i = 1;
return this.props.priceRangeFilters.map(filter => {
i++
return (
<Filter
key={filter.priceRange+i+"priceRange"}
name={this.props.match.params.type + "priceFilter"}
id={filter.priceRange}
changeFilterResetPageNumber={() => {this.props.changePriceFilter(filter); this.handlePageChange(1)}}
inuse={filter.inuse}
/>
)
})
}
filterDivExtenionToggle () {
var filterDivExtension = document.querySelector('.filterDivExtension');
var chevronUp = document.querySelector('#chevronUp');
var chevronDown = document.querySelector('#chevronDown');
var icon;
if (filterDivExtension.style.display === 'block') {
filterDivExtension.style.display = 'none';
chevronUp.style.display = 'none';
chevronDown.style.display = 'block';
} else {
filterDivExtension.style.display = 'block';
chevronUp.style.display = 'block';
chevronDown.style.display = 'none';
}
}
createProductsList() {
if(this.props.products.length > 0) {
return this.props.products.map(product =>{
if (this.props.products.indexOf(product) >= (this.state.activePage*12) - 12 && this.props.products.indexOf(product) < (this.state.activePage*12)) { //render the 12 (number of products per page) products that correspond to the current (active) page
return (
<ProductsListItem
key={product.id}
brand={product.brand}
model={product.model}
price={product.price}
image={product.image}
link={"/"+this.props.match.params.type+"/"+product.id}
/>
)
}
})} else {
return <div>No products match the filter criteria selected above.</div>
}
}
state = {
activePage: 1
}
handlePageChange(pageNumber) {
this.setState({activePage: pageNumber});
}
createPagination() {
if (this.props.products.length > 12) {
if (this.props.products.length > this.state.activePage * 12 && this.state.activePage > 1) { //if there are products following AND preceding the current page
return (
<Pagination
onclick1={() => this.handlePageChange(this.state.activePage - 1)}
onclick2={() => this.handlePageChange(this.state.activePage + 1)}
disabled1={false}
disabled2={false}
/>
)
} else if (this.props.products.length > this.state.activePage * 12) { //if there are only products following the current page
return (
<Pagination
onclick1={() => this.handlePageChange(this.state.activePage - 1)}
onclick2={() => this.handlePageChange(this.state.activePage + 1)}
disabled1={true}
disabled2={false}
/>
)
} else if (this.state.activePage > 1) { //if there are only products preceding the current page
return (
<Pagination
onclick1={() => this.handlePageChange(this.state.activePage - 1)}
onclick2={() => this.handlePageChange(this.state.activePage + 1)}
disabled1={false}
disabled2={true}
/>
)
}
}
}
render () {
return (
<div>
<div className="container">
{this.createCategoryOverview()}
<div className="row">
<div className= "filterDiv col-12">
<div className="iconCrossbar">
<i id="chevronDown" className="fa fa-chevron-down" onClick={this.filterDivExtenionToggle}></i>
<i id="chevronUp" className="fa fa-chevron-up" onClick={this.filterDivExtenionToggle}></i>
</div>
<div className="filterDivExtension">
<div className="row">
<div className="filtersList col-md-6 col-12">
Filter by Brand:
<div>
{this.createBrandFilterList()}
</div>
</div>
<div className="filtersList col-md-6 col-12">
Filter by Price Range:
<div>
{this.createPriceRangeFilterList()}
</div>
</div>
</div>
</div>
</div>
</div>
<div className="row productsList">
{this.createProductsList()}
</div>
{this.createPagination()}
</div>
</div>
)
}
};
function mapStateToProps(state , ownProps) {
let brandFilters = state.brandFilters;
let filtered_brandFilters = brandFilters;
filtered_brandFilters = filtered_brandFilters.filter(
filter => filter.type === ownProps.match.params.type //gets type from the the route params and finds products which have type that matches
)
let priceRangeFilters = state.priceRangeFilters;
let filtered_priceRangeFilters = priceRangeFilters;
filtered_priceRangeFilters = filtered_priceRangeFilters.filter(
filter => filter.type === ownProps.match.params.type
)
let overviews = state.overviews;
let overview = overviews.filter(
overview => overview.type === ownProps.match.params.type
)
let products = state.products;
let filtered_products = products;
filtered_products = filtered_products.filter(
product => product.type === ownProps.match.params.type
)
let activeBrandFilters = filtered_brandFilters.filter(
item => item.inuse === true
);
activeBrandFilters.forEach(filter => {
if (filter.brand != "ALL") {
filtered_products = filtered_products.filter(
product => product.brand === filter.brand
)
}
});
let activePriceRangeFilters = filtered_priceRangeFilters.filter(
item => item.inuse === true
);
activePriceRangeFilters.forEach(filter => {
if (filter.priceRange != "ALL") {
filtered_products = filtered_products.filter(
product => product.priceRange === filter.priceRange
);
}
});
let key = ownProps.match.params.type;
return {
overview: overview,
brandFilters: filtered_brandFilters,
priceRangeFilters: filtered_priceRangeFilters,
products: filtered_products,
key: key //a change of key property means the component remounts. this was needed so that when on a second page of products (state is activePage: 2) and switching to a 'page' with products type that does not have a second page (uses same components but displays different type of products), no products would be displayed because the component did not remount and thh state remained the same (activePage did not reset to 1)
};
};
function mapDispatchToProps(dispatch){
return bindActionCreators({changeBrandFilter: changeBrandFilter, changePriceFilter: changePriceFilter}, dispatch);
};
export const ProductsPageContainer = connect(mapStateToProps, mapDispatchToProps)(ProductsPage);
Related
Updated code: Im trying to first display carsList and only when selectedMake is selected, I would update the state with the result from filter and show another array. I tried storing carsList in updatedCarsList so it has all the cars on page load but Im missing something here.
CarOffers.jsx
const CarOffers = () => {
const [carsList, setCarsList] = useState([]);
const [updatedCarsList, setUpdatedCarsList] = useState([]);
const [selectedMake, setSelectedMake] = useState(undefined);
const getCars = () => {
axios.get(url)
.then((response) => {
return setCarsList(response.data)
})
}
const handleMakeChange = (select) => {
setSelectedMake(select.value)
}
const applyFilters = () => {
let updatedCarsList = carsList
if(selectedMake) {
updatedCarsList = carsList.filter(car => car.make === selectedMake)
setUpdatedCarsList(updatedCarsList);
} else {
setUpdatedCarsList(carsList)
}
}
useEffect(() => {
getCars()
applyFilters()
}, [ selectedMake ]);
return (
<div className="mka__wrapper-car-offers">
<div className="mka__container">
<div className="mka__content-car-offers">
<div className="mka__content-grid-offers">
<div className="item1">
< CarSlider/>
<div className="mka-responsive-item">
< DisplayCars/>
< SortingCars/>
< CarAlignment/>
</div>
</div>
<div className="item2">
<div className="mka__side-bar-divider">
< Search
carsList={carsList}/>
</div>
<div>
< FilterSideBar
carsList={carsList}
handleMakeChange={handleMakeChange} />
</div>
</div>
<div className="item3">
<Cars updatedCarsList={updatedCarsList}/>
</div>
</div>
</div>
</div>
</div>
)
}
export default CarOffers;
Cars.jsx
const Cars = ({ updatedCarsList }) => {
return (
<div className='mka__cars-grid'>
{updatedCarsList.map(car =>
<CarsItem key={car.id} car={car}/>)}
</div>
)
}
export default Cars
CarItem.jsx
const CarsItem = ({car: {year,month,transmission,mileage,price,title,link}}) => {
return (
<Fragment>
<div className="cars-item_wrapper">
<div className="cars-item_image">
<img src={link} alt="car" />
</div>
<div>
<a
className="cars-item_car-title"
href="/"
>
{title}
</a>
</div>
<div className=" cars-item_separator"></div>
<p className="cars-item_car-text">{price}</p>
</div>
</Fragment>
)
}
export default CarsItem
Move your applyFilters above getCars
Does Select need to be in <>
distinctBy... urgh.. use Set const unique = [...new Set(data.map(item => item.value))]
applyFilters... axios is async, but your setting a value so state doesn't update so no re-render? Maybe.
selectedMake - don't use null as a default, use undefined.
Hope that helps, feels like a state management issue.
... think its this ....
You are using carsList as your list of cars, however you are setting the value of carsList with setCarsList(updatedCarsList)... updatedCarsList is a filtered list of cars... only car => car.make === selectedMake so once you've selected a make your carList is only cars with the selected make.
Solution is to
Either separate the list from the filtered list
or preferably keep list, but pass the filtered state to the component that needs it... but not update state of the original list by calling setCarsList(updatedCarsList);
if (selectedMake){
updatedCarsList = updatedCarsList.filter(
car => car.make === selectedMake
)
};
setCarsList(updatedCarsList);
I've been trying to figure this out for a couple days & haven't had any luck after reviewing similar questions.
The page loads the first menu item. But, when you click on it, only console.log(page) goes off & the following <div>{page}</div> don't appear on the page.
import React from 'react';
import { Component } from 'react';
import ReactDOM from 'react-dom/client';
import { Collapse } from 'react-bootstrap';
class Menu extends Component {
constructor(props) {
super(props);
this.state = {
pages: ['a', 'b', 'c'],
selectedPage: 'a',
isOpen: false,
};
}
toggle() {
this.setState({isOpen: !this.state.isOpen})
}
selectPage(page) {
this.setState({ selectedPage: {page}})
}
listPages = (
() => {
let p;
p = this.state.pages.filter( page => page !== this.state.selectedPage);
/*
nesting <div> in <>{}</> didn't fix
*/
p.map((page, index) => {
console.log(page)
return (
<div
className={page + ' menu-item'}
onClick={() => {this.selectPage(page); this.toggle();}}
key={index + 1}
>
{page}
</div>
)
}
);
}
)
render() {
return(
<div className='menu'>
<div
className={ this.state.selectedPage + ' menu-item'}
onClick={() => { this.toggle()} }
key={1}
>
{this.state.selectedPage}
</div>
<Collapse in={this.state.isOpen}>
<>
{this.listPages()}
</>
</Collapse>
</div>
)
}
}
the function listPages is being executed but it doesn't return anything to show, what you need to do is add a return to your function for the map like this:
listPages = () => {
let p;
p = this.state.pages.filter( page => page !== this.state.selectedPage);
/*
nesting <div> in <>{}</> didn't fix
*/
return p.map((page, index) => {
console.log(page)
return (
<div
className={page + ' menu-item'}
onClick={() => {this.selectPage(page); this.toggle();}}
key={index + 1}
>
{page}
</div>
)
}
);
}
While I'm trying to get in to React, I started a project and got stuck. Maybe some one can help me to find the issue. Bellow I explain what the app should do.
The user types a query in an input-box inside SearchBar.jsx
The SearchBar component passes the query to App.jsx and fires up fetchPhotos function, which starts an API request.
To sort out pagination, the App.jsx imports Pagination.jsx, which calculates the number of pictures in the response and displays pagination buttons.
The above works.
But now if you click on a pagination button, the value for page from Pagination component should be passed to App.jsx and so to fetchPhotos function (runs the API request).
I guess the problem is that the value of page never finds its way to App.jsx and so the API request is missing the value of page.
I spent hours but couldn't find a way to fix it, due to lack of knowledge. Could you please point me to the right direction and show me what is wrong with the code?
App.jsx
import React, { Component } from "react";
import axios from "axios";
import Pagination from "../Pagination";
import SearchBar from "../SearchBar";
import ListItem from "../ListItem";
import "./app.scss";
class App extends Component {
state = {
photos: [],
totalPhotos: 0,
perPage: 30,
currentPage: 1,
query: null
};
componentDidMount() {
this.fetchPhotos("gorilla", this.state.currentPage);
}
fetchPhotos = (inputValue, page) => {
const baseUrl = "https://api.unsplash.com/search/photos";
const options = {
headers: {
Authorization: `Client-ID ${process.env.REACT_APP_UNSPLASH_API_KEY}`
},
params: {
query: inputValue,
page: this.state.page,
per_page: this.state.perPage
}
};
axios
.get(baseUrl, options)
.then(response => {
this.setState({
photos: response.data.results,
totalPhotos: parseInt(response.headers["x-total"]),
currentPage: page,
query: inputValue
});
})
.catch(() => {
console.log("Error");
});
};
render() {
return (
<div className="app">
<SearchBar onSubmit={this.fetchPhotos} />
<Pagination
current={this.state.currentPage}
total={this.state.totalPhotos}
perPage={this.state.perPage}
query={this.state.query}
onPageChanged={query => this.fetchPhotos(this.state.query)}
/>
<List data={this.state.photos} />
</div>
);
}
}
const List = ({ data }) => {
var items = data.map(photo => <ListItem key={photo.id} photo={photo} />);
return <div className="grid">{items}</div>;
};
export default App;
SearchBar.jsx
import React, { Component } from "react";
class SearchBar extends Component {
state = {
inputValue: ""
};
handleFormSubmit = e => {
e.preventDefault();
this.props.onSubmit(this.state.inputValue);
};
render() {
return (
<div className="header">
<h1>Search for images on Unsplash</h1>
<form onSubmit={this.handleFormSubmit} className="ui form">
<input
type="text"
placeholder="Type here to search for images"
value={this.state.inputValue}
onChange={e => this.setState({ inputValue: e.target.value })}
/>
</form>
</div>
);
}
}
export default SearchBar;
Pagination.jsx
import React, { Component } from "react";
class Pagination extends Component {
pages() {
var pages = [];
for (var i = this.rangeStart(); i <= this.rangeEnd(); i++) {
pages.push(i);
}
return pages;
}
rangeStart() {
var start = this.props.current - this.props.pageRange;
return start > 0 ? start : 1;
}
rangeEnd() {
var end = this.props.current + this.props.pageRange;
var totalPages = this.totalPages();
return end < totalPages ? end : totalPages;
}
totalPages() {
return Math.ceil(this.props.total / this.props.perPage);
}
nextPage() {
return this.props.current + 1;
}
prevPage() {
return this.props.current - 1;
}
hasFirst() {
return this.rangeStart() !== 1;
}
hasLast() {
return this.rangeEnd() < this.totalPages();
}
hasPrev() {
return this.props.current > 1;
}
hasNext() {
return this.props.current < this.totalPages();
}
changePage(page) {
this.props.onPageChanged(page);
console.log("Page inside Pagination", page);
}
render() {
return (
<div className="pagination">
<div className="pagination__left">
<span
role="button"
className={!this.hasPrev() ? "hidden" : ""}
onClick={e => this.changePage(this.prevPage())}
>
Prev
</span>
</div>
<div className="pagination__mid">
<ul>
<li className={!this.hasFirst() ? "hidden" : ""}>
<span role="button" onClick={e => this.changePage(1)}>
1
</span>
</li>
<li className={!this.hasFirst() ? "hidden" : ""}>...</li>
{this.pages().map((page, index) => {
return (
<li key={index}>
<span
role="button"
onClick={e => this.changePage(page)}
className={this.props.current === page ? "current" : ""}
>
{page}
</span>
</li>
);
})}
<li className={!this.hasLast() ? "hidden" : ""}>...</li>
<li className={!this.hasLast() ? "hidden" : ""}>
<span
role="button"
onClick={e => this.changePage(this.totalPages())}
>
{this.totalPages()}
</span>
</li>
</ul>
</div>
<div className="pagination__right">
<span
className={!this.hasNext() ? "hidden" : ""}
onClick={e => this.changePage(this.nextPage())}
>
Next
</span>
</div>
</div>
);
}
}
Pagination.defaultProps = {
pageRange: 2
};
export default Pagination;
I think your error is at `onChange', because you are giving current state query to fetch instead of the new query:
onPageChanged={query => this.fetchPhotos(this.state.query)}
You should replace it for new query like:
onPageChanged={query => this.fetchPhotos(query)}
EDIT 1:
You can see working it on https://codesandbox.io/s/9ymjj8ko9p?fontsize=14.
The changes is just as I said, on App.jsx:
params fixed passing page from function params and not from
fix onPageChange prop to Pagination like:
onPageChanged={page => this.fetchPhotos(this.state.query, page)}
I have a Like component on a product page. The component is a button that when clicked, that product is put in the states liked array. The problem is when I change products and click the button the second product is not added to the array, rather it replaces the other product that's in the array. For example. I click on a shirt, I see that its been added to the array. I go back and click another shirt and instead of both shirts being in the array only the last shirt I clicked is in the array. Below is the product details page that includes the Like component and the Like page
class Details extends Component {
render() {
const { id } = this.props.match.params;
const productList = this.props.products.map(product => {
if (product.product_id == id) {
return (
<div className="container product_details">
<div className="left">
<div className="images">
<img className="detail_main-photo" src={product.image} />
</div>
</div>
<div className="right">
<h2 className="detail_heading">{product.title}</h2>
<p className="detail_description">{product.description}</p>
<div className="detail_size">
</div>
<p className="detail_price">${product.price}</p>
<Likes product={product} />
</div>
</div>
)
}
})
return (
<div >
{productList}
</div>
)
}
}
export default Details;
class Likes extends Component {
state = {
likes: []
}
addLike = () => {
const button = document.querySelector('.submit')
const Liked = this.state.likes.findIndex(like => {
return like.id === this.props.product.product_id
})
if (Liked) {
const { product } = this.props;
const newLike = {
id: product.product_id,
image: product.image[0],
title: product.title,
price: product.price
}
this.setState({
likes: [...this.state.likes, newLike]
})
}
}
render() {
console.log(this.state)
return (
<button className="submit" onClick={this.addLike}>Add To Wishlist</button>
)
}
}
export default Likes;
Just to mention, I did not find an answers for my particular situation on similar post.
My app allows a user to get a city's weather along with a relevant packing list for that weather.
I want to make each city's card deletable and have an 'X' placed that should call an updateTerms action creator, which would in turn remove it from the users list and update the dom.
The callback seems to fire, but this.props.updateTerms throws **TypeError: Cannot read property 'props' of null
at handleDeleteCard **
a_result_card.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import ATodoList from './a_Todo_List';
import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
import { bindActionCreators} from 'redux';
import { updateTerms } from '../actions';
class AResultCard extends Component {
constructor(props) {
super(props);
this.state = { cityName : '' };
this.handleDeleteCard = this.handleDeleteCard.bind(this);
}
handleDeleteCard(event) { // I NEED TO CALL THIS WITH THE KEY
//So THAT I CAN REMOVE IT FROM THE USERS TERM LIST
event.preventDefault();
debugger;
this.props.updateTerms(cityName, "delete");
}
renderWeather(cityData) {
function calcAverageTemp(days) {
let reduced = days.reduce((sum,day) => {
return sum + day.main.temp_max;
}, 0);
return Math.floor(reduced / cityData.list.length);
};
function calcAverageHumidity(days) {
let reduced = days.reduce((sum,day) => {
return sum + day.main.humidity;
}, 0);
return Math.floor(reduced / cityData.list.length);
};
const avgTempK = calcAverageTemp(cityData.list);
const cityName = cityData.city.name;
const avgTempF = Math.floor((avgTempK * 1.8) - 459.67 );
const avgHumidity = calcAverageHumidity(cityData.list);
return (
<li key={cityName} className="result-card">
<div className="card">
<div className="">
<div className="weather-details">
<div className="card-heading">
<span className="weather-detail detail-title">{cityName}</span>
<span className="weather-detail">{avgTempF} °F</span>
<span className="weather-detail">H: {avgHumidity}%</span>
<span className="weather-detail delete-card-icon" onClick={handleDeleteCard}>X</span>
</div>
</div>
<ATodoList avgTempF={avgTempF} />
</div>
</div>
</li>
);
}
render() {
return (
<ul className="results">
<CSSTransitionGroup
transitionName="example"
transitionEnterTimeout={150}
transitionLeaveTimeout={150}>
{this.props.weather.map(this.renderWeather)}
</CSSTransitionGroup>
</ul>
);
}
}
function mapStateToProps({weather, userTerms}) {
return ({
weather,
userTerms
});
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ updateTerms }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(AResultCard);
You need to call your handleDeleteCard function like
<span className="weather-detail delete-card-icon" onClick={this.handleDeleteCard}>X</span>
since it belongs to the context of the React Component and not the renderWeather function
Also bind the renderWeather function
renderWeather = (cityData) => {
function calcAverageTemp(days) {
let reduced = days.reduce((sum,day) => {
return sum + day.main.temp_max;
}, 0);
return Math.floor(reduced / cityData.list.length);
};
function calcAverageHumidity(days) {
let reduced = days.reduce((sum,day) => {
return sum + day.main.humidity;
}, 0);
return Math.floor(reduced / cityData.list.length);
};
const avgTempK = calcAverageTemp(cityData.list);
const cityName = cityData.city.name;
const avgTempF = Math.floor((avgTempK * 1.8) - 459.67 );
const avgHumidity = calcAverageHumidity(cityData.list);
return (
<li key={cityName} className="result-card">
<div className="card">
<div className="">
<div className="weather-details">
<div className="card-heading">
<span className="weather-detail detail-title">{cityName}</span>
<span className="weather-detail">{avgTempF} °F</span>
<span className="weather-detail">H: {avgHumidity}%</span>
<span className="weather-detail delete-card-icon" onClick={this.handleDeleteCard}>X</span>
</div>
</div>
<ATodoList avgTempF={avgTempF} />
</div>
</div>
</li>
);
}