Passing the value of page from Pagination.jsx to App.jsx - reactjs

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)}

Related

How to get the promise value in reactjs?

I am trying to render multiple images, stored on firebase, on a single page.
This is the function to get the image url:
async getTokenImage(fileName) {
const ref = firebase.storage().ref('token/' + fileName);
const url = await ref.getDownloadURL();
console.log('in here')
return url
}
This is the function call:
<div
class="card-img-top"
style={{
backgroundColor: '#'+token.color,
backgroundImage: 'url('+this.getTokenImage(token.fileName).then((value) => {return value})+')'
}}></div>
console logging the "value" gives the needed image url
but returning the "value" gives nothing.
for the sake of testing, I have done this:
let image = this.getTokenImage(token.fileName).then((result) => {return result})
console.log(image)
which results in:
So how do I get the image url within the initial function call?
Part of grandfather component:
<MintedTokens
account={this.state.account}
contract={this.state.contract}
tokens={this.state.tokens} />
Father component:
import React from 'react';
import "./minted-tokens.scoped.css";
import MultipleMintedTokens from './multiple-minted-tokens.js';
import SingleMintedTokens from './single-minted-token.js';
class MintedTokens extends React.Component {
render() {
const color = window.location.pathname.split('/')[2]
let display
if(color == undefined) {
display = <MultipleMintedTokens
account={this.props.account}
tokens={this.props.tokens}></MultipleMintedTokens>
} else {
display = <SingleMintedTokens
account={this.props.account}
color={color}
tokens={this.props.tokens}></SingleMintedTokens>
}
return (
<div class="Minted-Token">
{display}
</div>
);
}
}
export default MintedTokens;
Child component (where the multiple images need to be rendered):
import React from 'react';
import ether from '../../assets/ether.svg'
import firebase from "../../firebase.js"
import { Link } from 'react-router-dom';
class MultipleMintedTokens extends React.Component {
async getTokenImage(fileName) {
const ref = firebase.storage().ref('token/' + fileName);
const url = await ref.getDownloadURL();
console.log('in here')
return url
}
render() {
return (
<div class="welcome minted-tokens">
<h1 class="">Minted Tokens</h1>
<div class="row">
{this.props.tokens.map((token, key) => {
let name = '',
description = ''
if(token.to == this.props.account) {
token.name.length >= 17 ? name = token.name.slice(0,17) + '...' : name = token.name
token.description.length >= 28 ? description = token.description.slice(0,29) + '...' : description = token.description
let image = this.getTokenImage(token.fileName).then((result) => {return result})
console.log(image)
return (
<Link to={token.tokenURI} key={key}>
<div class='token-id'>{token.id}</div>
<div class="card card-width">
<div
class="card-img-top"
style={{
backgroundColor: '#'+token.color,
backgroundImage: 'url('+this.getTokenImage(token.fileName).then((value) => {return value})+')'
}}></div>
<h5 class="card-header" style={{
backgroundColor: '#'+token.color,
color: '#'+token.fontColor}}>{name}</h5>
<div class="card-body">
<p class="card-text">{description}</p>
<div class="foot-of-card">
<span class="row price-row">
<img src={ether} alt="ether" class="icon" />
<p class="card-text">{token.price}</p>
</span>
<p class="card-text datetime">{token.dateTime}</p>
</div>
</div>
</div>
</Link>
)
}
})}
</div>
</div>
);
}
}
export default MultipleMintedTokens;
I could not test it well but you can try the following:
Add:
constructor(props) {
super(props);
this.state = {
urlImgs: [],
};
}
...
componentDidMount() {
const { tokens } = this.props;
const promiseArray = tokens.map((token) => getTokenImage(token.fileName));
Promise.all(promiseArray)
.then(valueArray => {
this.setState(prevState => ({
...prevState,
urlImgs: valueArray
}))
})
.catch(err => console.log(err));
}
...
backgroundImage: `url(${urlImgs[key] ?? null})`
All Code:
class MultipleMintedTokens extends React.Component {
constructor(props) {
super(props);
this.state = {
urlImgs: [],
};
}
async getTokenImage(fileName) {
const ref = firebase.storage().ref('token/' + fileName);
const url = await ref.getDownloadURL();
console.log('in here')
return url
}
componentDidMount() {
const { tokens } = this.props;
const promiseArray = tokens.map((token) => getTokenImage(token.fileName));
Promise.all(promiseArray)
.then(valueArray => {
this.setState(prevState => ({
...prevState,
urlImgs: valueArray
}))
})
.catch(err => console.log(err));
}
render() {
const { urlImgs } = this.state;
return (
<div class="welcome minted-tokens">
<h1 class="">Minted Tokens</h1>
<div class="row">
{this.props.tokens.map((token, key) => {
let name = '',
description = ''
if(token.to == this.props.account) {
token.name.length >= 17 ? name = token.name.slice(0,17) + '...' : name = token.name
token.description.length >= 28 ? description = token.description.slice(0,29) + '...' : description = token.description
let image = this.getTokenImage(token.fileName).then((result) => {return result})
console.log(image)
return (
<Link to={token.tokenURI} key={key}>
<div class='token-id'>{token.id}</div>
<div class="card card-width">
<div
class="card-img-top"
style={{
backgroundColor: '#'+token.color,
backgroundImage: `url(${urlImgs[key] ?? null})`
}}></div>
<h5 class="card-header" style={{
backgroundColor: '#'+token.color,
color: '#'+token.fontColor}}>{name}</h5>
<div class="card-body">
<p class="card-text">{description}</p>
<div class="foot-of-card">
<span class="row price-row">
<img src={ether} alt="ether" class="icon" />
<p class="card-text">{token.price}</p>
</span>
<p class="card-text datetime">{token.dateTime}</p>
</div>
</div>
</div>
</Link>
)
}
})}
</div>
</div>
);
}
}
export default MultipleMintedTokens;
if you have React with version > 16.8, I advice you to start using Stateless Components and Hooks. And read about the lifecycle of React and the render method here:

React, how to return at index 0 if the index is bigger than array length

I am making a photo slider in React using class components.
How can I acesse the variable photos from the nextSlide and previousSlide?
I tried to move the variable Photos up next to the state but it is throwing me an error like "const can be used in type scipt"
Thank you if you have any sugesstion
export default class Gallery extends Component {
state = { index: 0 }
nextSlid() {
this.setState({index: this.state.index +1})
}
previousSlid() {
this.setState({index: this.state.index -1})
}
render() {
const photos = this.props.Photos;
return (
<div className="photo-box">
<div className="left-icon" onClick={() => this.previousSlid()}>
<FiChevronLeft color='white' size={70}/>
</div>
<div className="right-icon" onClick={() => this.nextSlid()}>
<FiChevronRight color='white' size={70}/>
</div>
<img src={photos[this.state.index]} alt="Foto"/>
</div>
)
}
}
Code Example photo
nextSlid() {
this.setState({index: (this.state.index +1) % photos.length })
}
previousSlid() {
this.setState({index: (this.state.index -1) % photos.length })
}

Todo App in React- Wanted to add button which when clicks deletes the whole todo list

I have created a ToDo App in React. I want to add a single button which when I clicked on removes the whole todo list and shows the message to the user "You don't have any todo's". I am trying to add functionality but can't seem to find a perfect way.
I have given all the Todos a unique id and I also to grab these id's but don't how to use them to remove all Todos from a single button only. Help me. Thanks in advance
here is my main component App.js
import React, { Component } from 'react';
import PrintTodo from "./printtodo"
import Addtodo from "./addTodo"
class App extends Component {
state = {
todos: [
{id:1, content:"Buy Tomatoes"},
]
}
deleteTodo = (id) => {
const todos = this.state.todos.filter(todo => {
return todo.id !== id
})
this.setState({
todos
})
}
addTodo = (todo) => {
todo.id = Math.random()
// console.log(todo)
let todos = [...this.state.todos, todo]
this.setState({
todos
})
}
button = () => {
// console.log(this.state)
const allTodos = this.state.todos.filter(todo => {
console.log(todo)
})
// const id = 10;
// console.log(allTodos)
// allTodos.forEach(todo => {
// // console.log(todo)
// const arr = new Array(todo)
// arr.pop()
// })
}
render(){
// console.log(this.state)
return (
<div className="App">
<div className="container">
<header className="text-center text-light my-4">
<h1>ToDo - List</h1>
<form>
<input type="text" name="search" placeholder="Search ToDo's" className="form-control m-auto"/>
</form>
</header>
<PrintTodo addTodo={this.state.todos} deleteTodo={this.deleteTodo}/>
<Addtodo addTodo={this.addTodo} allTodos={this.button}/>
</div>
</div>
)
}
}
export default App;
PrintTodo Component
import React from 'react'
const printTodo = ({addTodo, deleteTodo, }) => {
// console.log(addTodo)
const todoList = addTodo.length ? (
addTodo.map(todo => {
return (
<ul className="list-group todos mx-auto text-light" key={todo.id}>
<li className="list-group-item d-flex justify-content-between align-items-center">
<span>{todo.content}</span>
<i className="far fa-trash-alt delete" onClick={()=>{deleteTodo(todo.id)}}></i>
</li>
</ul>
)
})
) : (
<p className="text-center text-light">You don't have any ToDo's</p>
)
return (
<div>
{todoList}
</div>
)
}
export default printTodo
AddTodo Component
import React, { Component } from 'react'
class Addtodo extends Component{
state = {
content: ""
}
handleChange = (e) => {
this.setState({
content: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault()
this.props.addTodo(this.state)
this.setState({
content: ""
})
}
render(){
// console.log(this.props.allTodos)
return(
<div>
<form className="text-center my-4 add text-light" onSubmit={this.handleSubmit}>
<label htmlFor="add">Add a New ToDo</label>
<input onChange={this.handleChange} type="text" name="add" id="add" className="form-control m-auto" value={this.state.content}/>
</form>
<button onClick={() => {this.props.allTodos()}}>Clear Whole List</button>
</div>
)
}
}
export default Addtodo
In your app.js make this your button component.
button = () => {
this.setState({todos: []})
})
Resetting your todos to an empty array will delete all your todos.

Scroll on select box not on page

I am fetching data from an API and then displaying it as options in the select boxes. The option chosen from the first select box will populate the options for second select box. and after that we will submit the form which will redirect us to a different page. This whole functionality is working fine.
The scrolling in the select boxes is working fine on keyboard keys press, dragging the scroll bar.
I am stuck at the point that whenever I scroll over the options the page gets scrolled instead of the options dropdown and I don't want to use Jquery as it is not suggested to be used with ReactJs. Is their any option other than Jquery to perform this operation?
import React from 'react';
import { Route, Redirect } from 'react-router'
import { Async } from 'react-select';
import Select from 'react-select'
import { renameKeys } from '../utils'
export class ExplorePage extends React.Component {
constructor() {
super()
this.state = {
cityValue: null,
universityValue: 0,
unselectedUnivValue: [],
os: "",
classes: "",
class2: ""
}
this.universitySelectChange = this.universitySelectChange.bind(this);
this.citySelectChange = this.citySelectChange.bind(this);
this.getCities = this.getCities.bind(this)
this.getUniv = this.getUniv.bind(this)
this.osCheck = this.osCheck.bind(this);
}
osCheck() {
this.state.os = navigator.platform;
console.log(this.state.os);
if (this.state.os == 'Win32') {
this.state.classes = document.getElementsByClassName('Select-placeholder');
for (var i = 0; i < this.state.classes.length; i++) {
this.state.classes[i].classList.add('for-Win32');
}
this.state.class2 = document.getElementsByClassName('Select-value');
for (var i = 0; i < this.state.class2.length; i++) {
this.state.class2[i].classList.add('for-Win32');
}
}
}
componentDidMount() {
this.osCheck();
}
componentWillUnmount() {
this.osCheck();
}
universitySelectChange = (universityValue) => {
this.setState({ universityValue });
}
citySelectChange = (cityValue) => {
this.setState({ cityValue });
this.getUniv(cityValue).then((options) => { this.setState({ unselectedUnivValue: options }) })
}
getCities() {
return fetch(`API`)
.then((response) => response.json())
.then((json) => {
let newKey = { city_id: 'value', name: 'label' }
return {
options: json.response.result.map((item) => renameKeys(item, newKey))
}
})
}
getUniv(cityValue) {
return fetch(`API`)
.then((response) => response.json())
.then((json) => {
let newKey = { university_id: 'value', name: 'label' }
let toReturn = json.response.result.map((item) => renameKeys(item, newKey))
return toReturn
})
}
render() {
return <section id="scroll-section6" className="explore-res img-bg height-70 d-flex align-items-center">
<div className="container">
<div className="row">
<div className="col-lg-12 mt-5 pt-5">
<div className="st-sec-content">
<h1 className="st-sec-heading">EXPLORE RESIDENCES</h1>
<form>
<ul className="res-prop-select-group">
<li>
<div className="city-icon"></div>
<Async
onChange={() => { this.osCheck(), this.citySelectChange }}
loadOptions={this.getCities}
placeholder="Select your City"
removeSelected={this.state.removeSelected}
simpleValue
value={this.state.cityValue}>
</Async>
</li>
<li>
<div className="country-icon"></div>
<Select multi
onChange={() => { this.osCheck(), this.universitySelectChange }}
options={this.state.unselectedUnivValue}
placeholder="Select your College"
removeSelected={this.state.removeSelected}
simpleValue
value={this.state.universityValue}>
</Select>
</li>
<li>
<button className="st-l-hm-btn-1 a-link go mr-0">
<a class="text-white font-weight-light"
href={this.state.cityValue === null ? '/residence' : `/residence/${this.state.cityValue}/${this.state.universityValue}`}
onClick={(e) => {
}}>Go</a></button>
</li>
</ul>
</form>
</div>
</div>
</div>
</div >
</section >
}
}
Have you tried using focus() method
You could use the above focus method on the select ref
or you could also set autofocus prop on the select like so
<Select multi onChange={()=>
{this.osCheck(), this.universitySelectChange }}
autoFocus={true}
options={this.state.unselectedUnivValue}
placeholder="Select your College"
removeSelected={this.state.removeSelected}
simpleValue
value={this.state.universityValue}>
</Select>

pagination using react, react-router, and redux [duplicate]

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);

Resources