Paginate API call in React Component - javascript

I want to paginate the reults of an Api call.
I am making an Api Call by using Axios like this
apiCall() {
const API = `http://www.omdbapi.com/`;
axios.get(API, {
params: {
apikey: process.env.REACT_APP_MOVIECALL_API_KEY,
type: 'movie',
s: 'superhero',
page: this.state.pageCount
}
})
.then(res => {
const superheroes = res.data.Search
const totalResults= parseInt(res.data.totalResults)
this.setState({
totalResults
});
this.setState({
superheroes
})
})
.catch((error) => {
console.log(error);
});
}
When the component is mounted the function called, as such
componentDidMount()
{
this.apiCall();
}
In the render function I map over each search result (in the api call the s param is the search option)
and for each result I display a button, that when clicked displays the related info of that movie.
The api by default displays 10 results per call but this particular search has 123 results in total.
Now by updating the param page in the call which I've set to this.state.pageCount
it displays 10 different movies relating to that page, at the moment I hardcode the pageCount inside the state to make sure it works and the corresponding page number shows the right list of 10 movies.
Now I would like to paginate the results by updating the page number, so when you click on next or the number 3/4/5 then the component loads the correct corresponding results, I've tried a couple of option relating to react but they somehow don't update the page number.
If someone could point me in the right direction or knows a simple solution, I am all ears.
the following code is the whole component, to get an idea of what I am trying to do.
What I have so far seems to be working, so what I am asking is for a
simpler more elegant way of doing pagination for this particular
situation.
export class MovieDetails extends Component {
constructor(props){
super(props)
this.state = {
superheroes: [],
clicked: false,
activeHero: {},
pageCount: 11,
totalResults: null,
currentPage: 1
}
this.handleClick = this.handleClick.bind(this);
}
handleClick(hero) {
const checkActive = this.state.activeHero.imdbID === hero.imdbID
const activeHero = {...hero, active: !checkActive}
this.setState({
clicked: !this.state.clicked,
activeHero
})
}
apiCall() {
const API = `http://www.omdbapi.com/`;
axios.get(API, {
params: {
apikey: process.env.REACT_APP_MOVIECALL_API_KEY,
type: 'movie',
s: 'superhero',
page: this.state.pageCount
}
})
.then(res => {
const superheroes = res.data.Search
const totalResults = parseInt(res.data.totalResults)
this.setState({
totalResults
});
this.setState({
superheroes
})
})
.catch((error) => {
console.log(error);
});
}
componentDidMount() {
this.apiCall();
}
handlePageChange = (page, e) => {
this.setState({
currentPage: page
});
this.apiCall(this.setState({pageCount: page}))
};
render() {
const {superheroes, currentPage } = this.state
return (
<div>
{
superheroes.map((hero, i) =>
<div className="Results" key={i}>
<button onClick={() => {this.handleClick(hero)}}>
<h1>{hero.Title}</h1>
{
this.state.clicked && this.state.activeHero.imdbID === hero.imdbID
? <ul>
{<div key={i}>
Movie Title: <h2> {hero.Title}</h2>
Year of Release: <h2>{hero.Year}</h2>
ID: <h2>{hero.imdbID}</h2>
<div><img className="Poster" alt="movieposter" src={hero.Poster}/></div>
</div>
}
</ul>
: null
}
</button>
</div>)
}
<div className="Pagination">
<Pagination
total={this.state.totalResults}
limit={10}
pageCount={this.state.pageCount}
currentPage={currentPage}
>
{({
pages,
currentPage,
hasNextPage,
hasPreviousPage,
previousPage,
nextPage,
totalPages,
getPageItemProps
}) => (
<div>
<button
{...getPageItemProps({
pageValue: 1,
onPageChange: this.handlePageChange
})}
>
first
</button>
{hasPreviousPage && (
<button
{...getPageItemProps({
pageValue: previousPage,
onPageChange: this.handlePageChange
})}
>
{'<'}
</button>
)}
{pages.map(page => {
let activePage = null;
if (currentPage === page) {
activePage = { backgroundColor: '#fdce09' };
}
return (
<button
{...getPageItemProps({
pageValue: page,
key: page,
style: activePage,
onPageChange: this.handlePageChange
})}
>
{page}
</button>
);
})}
{hasNextPage && (
<button
{...getPageItemProps({
pageValue: nextPage,
onPageChange: this.handlePageChange
})}
>
{'>'}
</button>
)}
<button
{...getPageItemProps({
pageValue: totalPages,
onPageChange: this.handlePageChange
})}
>
last
</button>
</div>
)}
</Pagination>
</div>
</div>
);
}
}

In your axios.get function you are sending page: this.state.pageCount, however in your handlePageChange function you are setting state.currentPage which doesn't seem right to me.
I'm also a bit confused about the onPageChange event on <button />. Is this button a custom component you are importing (in which case it should be capatalised so that it's clear) or is it a HTML button? If it's a HTML button then you need to use the onClick event which will pass the event as and argument to the handlePageChange function. I'm guessing it's custom though from the props you're passing it so just worth checking that it's sending the page value through correctly.

Related

React open a popup of specific object when clicking on the object

I have images for every Project when i click on a image i want to open a modal for that specific object and display some info. But when i click on it now it opens a modal for every project i want it to only display a modal for the specific image i clicked on. ( on the image the red X button is the content that the modal renders and "Volvo Blog" is the projectName when selecting the image to the right.) when i click on the image to the right i want it to only display "Volvo Blog" under that image instead of all images
class ProjectsSection extends Component {
state = {
Projects: [],
openModal: false,
selectedModal: null,
};
async componentDidMount() {
const { data } = await getProjects();
this.setState({ Projects: data });
console.log(this.state.Projects);
}
openModal = project => {
this.setState({ openModal: true, selectedModal: project });
};
closeModal = project => {
this.setState({ openModal: false });
};
render() {
return (
<div className="Section">
<h1>Welcome to my Projects 😀</h1>
<ul className="Projects">
{this.state.Projects.map(project => (
<li key={project._id} className="ProjectsShowcase">
{" "}
when this image is clicked the modal opens.
<img
className="ProjectImage"
src={project.image}
alt=" of Project"
onClick={() => {
this.openModal(project);
console.log(project._id);
}}
/>
here i conditionally render the modal. EDIT: passed the values of the
selected object as props
{this.state.openModal ? (
<ProjectModal closeModal={this.closeModal}
projectName={this.state.selectedProject.projectName}
projectImage={this.state.selectedProject.image}
projectDescription={this.state.selectedProject.description}
/>
) : null}
</li>
))}
</ul>
</div>
);
}
}
I think that you could make life a bit simpler for yourself by reducing the amount of state. The final piece of the puzzle then is just what logical check you need to make to determine if the modal is there. Below is what I think you could use, with some explanation in the comments.
class ProjectsSection extends Component {
state = {
Projects: [],
// openModal: false, - don't need, can tell just by if we have an id or not
selectedModalId: null // use null to represent no selection
};
async componentDidMount() {
const { data } = await getProjects();
this.setState({ Projects: data });
}
openModal = (project) => {
this.setState({ selectedModalId: project._id }); // just use an id
};
closeModal = () => {
this.setState({ selectedModalId: null }); // just reset to initial state
};
render() {
return (
<div className="Section">
<h1>Welcome to my Projects 😀</h1>
<ul className="Projects">
{this.state.Projects.map((project) => {
// doing it with a variable for ease of reading
const shouldShowModal = project._id === this.state.selectedModalId;
return (
<li key={project._id} className="ProjectsShowcase">
<img
className="ProjectImage"
src={project.image}
alt=" of Project"
onClick={() => {
this.openModal(project);
}}
/>
{shouldShowModal ? (
// here note change to use the mapped project, not what is in state as that's more likely
// to result in problems down the line
<ProjectModal
closeModal={this.closeModal}
projectName={project.projectName}
projectImage={project.image}
projectDescription={project.description}
/>
) : null}
</li>
);
})}
</ul>
</div>
);
}
}

Input value in child component not being updated by setState

In this app, I'm fetching images from the Unsplash API (with an Express back end, React front end). On page load, general images appear (rendered inside the react-infinite-scroll-component), and if you search, a special fetch method (fetchSearchImages) is called to get new images. In either case, they're rendered in a react-infinite-scroll-component instance.
My problem is that after the form holding the search input is submitted, the search input isn't getting cleared. In the input I have value={props.inputValue}, and in the parent component, after the form is submitted fetchSearchImages is called. In fetchSearchImages, I'm trying to reset the input value with this.setState() but the value displayed in the input remains unchanged. I also tried to do so in the handleSubmit() else block, and that didn't do anything either.
View live | GitHub repo
Child search input component:
const SearchInput = props => {
const onSubmit = e => {
// Prevents GET request/page refresh on submit
e.preventDefault();
props.onFormSubmit();
};
return (
<form onSubmit={onSubmit}>
<div className="control">
<input autoFocus value={props.inputValue} onChange={e => props.onSearch(e.target.value)} className="input" type="text" placeholder="Search" />
</div>
</form>
);
}
Parent component:
export class Images extends Component {
state = {
images: [],
searchImages: [],
count: 4,
page: 1,
searchPage: 1,
term: '',
search: false,
newSearch: false,
blankSearch: false,
inputValue: ''
};
componentDidMount() {
const { page, count } = this.state;
axios
.get(`/api/photos?page=${page}&count=${count}`)
.then(res => this.setState({ images: res.data }));
// To prevent same images being fetched upon scrolling (in the first call to fetchImages)
this.setState({ page: page + count });
}
fetchImages = () => {
const { page, count, images } = this.state;
this.setState({ page: page + 1 });
axios
.get(`/api/photos?page=${page}&count=${count}`)
.then(res =>
this.setState({ images: images.concat(res.data) })
);
}
fetchSearchImages = () => {
const { searchPage, count, term, searchImages } = this.state;
this.setState({ searchPage: searchPage + 1, inputValue: '' });
axios
.get(`/api/photos/search?term=${term}&page=${searchPage}&count=${count}`)
.then(res =>
this.setState({
searchImages: searchImages.concat(res.data.results)
})
);
}
// Necessary to place fetchSearchImages in a setState callback to ensure other state is set first
handleSubmit = () => {
if (!this.state.inputValue) {
this.setState({
images: [],
blankSearch: true,
newSearch: false,
search: false,
searchImages: [],
searchPage: 1,
page: 1,
}, this.fetchImages);
} else {
this.setState({
term: this.state.inputValue,
searchImages: [],
searchPage: 1,
page: 1,
search: true,
newSearch: true
}, this.fetchSearchImages);
}
}
handleInputChange = (e) => {
this.setState({
inputValue: e
});
}
render() {
return (
<>
<SearchInput onSearch={this.handleInputChange} value={this.state.inputValue} onFormSubmit={this.handleSubmit} />
<div className="images">
<InfiniteScroll
dataLength={this.state.blankSearch ? this.state.images.length : (this.state.newSearch || this.state.search) ? this.state.searchImages.length : this.state.images.length}
next={this.state.search ? this.fetchSearchImages : this.fetchImages}
hasMore={true}
loader={
<div className="loader-dots">
<span className="loader-dot"></span>
<span className="loader-dot"></span>
<span className="loader-dot"></span>
<span className="loader-dot"></span>
</div>
}
>
{this.state.newSearch || this.state.search ? this.state.searchImages.map(image =>
<Image key={image.id + Math.random()} image={image} />
) : this.state.blankSearch ? this.state.images.map(image =>
<Image key={image.id + Math.random()} image={image} />
) : this.state.images.map(image =>
<Image key={image.id + Math.random()} image={image} />
)}
</InfiniteScroll>
</div>
</>
);
}
}
It doesn't look like your input is being properly controlled.
In your SearchInput component, you are referencing an invalid prop. You call the prop value in the parent, but reference it as inputValue in the child.
Change the input to:
<input autoFocus value={props.value} onChange={e => props.onSearch(e.target.value)} className="input" type="text" placeholder="Search" />
Or the parent to:
<SearchInput onSearch={this.handleInputChange} inputValue={this.state.inputValue} onFormSubmit={this.handleSubmit} />

How to fix ReactJS filtered search that isn't working properly

I am working on some reactjs project that will show data based on user query. User can type and filtered data by checkbox.
It working properly actually, user type something in search bar and record are fetching data correctly. The problem is when user use check box to filter some data, the exact data show up but when they remove/uncheck the checkbox, the filtered data is not show up, instead it show the whole data.
I want that, when user remove/uncheck the checkbox from that data they type, it show back the original data that user type.
I am sorry if my language is a little bit difficult to understand. I will provide my code please have a look and help me to figure out if I made some mistake, also please tell me what is the best practice to fix this problem. I am beginner to React and does not have much knowledge to fix this problem . Thanks
import React from 'react';
import logo from './logo.svg';
import { Dropdown } from 'semantic-ui-react'
import './App.css'
import DropdownCheckbox from './components/dropdownCheckbox/dropdownCheckbox';
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
furnitureStyles: [],
products: [],
checkedFS: [],
checkedDT: [],
searchFilt: "",
filteredProduct: []
};
};
async onDropdownChange(val){
await this.setState({
checkedFS: val
})
await this.filteredFurnitureStyle()
}
fetchData(){
fetch('http://www.mocky.io/v2/5c9105cb330000112b649af8')
.then( response => {
if(response.status === 200){
return response.json()
}
})
.then(responseJson => {
this.setState({
furnitureStyles: responseJson.furniture_styles,
products: responseJson.products,
filteredProduct: responseJson.products
})
})
}
componentDidMount(){
this.fetchData()
}
loadingPage(){
return(
<div>
Please Wait ...
</div>
)
}
regexDesc(value){
return value.replace(/(?<=^.{115}).*/, " ...")
}
async filteringFunc(e){
const { checkedDT, checkedFS, searchFilt, products, filteredProduct } = this.state
if(e.target.value !== "") {
let search = products.filter(product => product.name.toLowerCase().indexOf(e.target.value.toLowerCase()) !== -1)
this.setState({
filteredProduct : search
})
} else if(e.target.value === ""){
this.setState({
filteredProduct: products
})
}
}
async onDropdownChange(val){
await this.setState({
checkedFS: val
})
await this.filteredFurnitureStyle()
}
filteredFurnitureStyle = () => {
const { filteredProduct, checkedFS, products } = this.state
if(checkedFS.length > 0) {
let search = filteredProduct.filter(product => (
checkedFS.findIndex(element => product.furniture_style.indexOf(element) !== -1) !== -1
))
this.setState({
filteredProduct: search
})
} else {
this.setState({
filteredProduct: products
})
}
}
render(){
const { furnitureStyles, products, checkedDT, checkedFS, filteredProduct } = this.state
return (
<div className="App">
<header>
<div className="search-section">
<input type="search"
placeholder="Search Furniture ..."
className="search-input"
onChange={(e)=>this.filteringFunc(e)}
/>
</div>
<div className="dropdown-section"
>
{furnitureStyles.length > 0 ? (
<React.Fragment>
<DropdownCheckbox
style={{margin:"0 24px"}}
defaultSelected="Furniture Style"
options={furnitureStyles}
onChange={(val)=>this.onDropdownChange(val)}
/>
<DropdownCheckbox
style={{margin:"0 24px"}}
defaultSelected="Delivery Time"
options={["1 week","2 weeks", "1 Month", "more..."]}
onChange={val=>this.setState({
checkedDT: val
})}
/>
</React.Fragment>) : "Loading"
}
</div>
</header>
<div id="section2">
<div className="card-section">
{products.length > 0 &&
filteredProduct.map(product => {
return (
<div className="ui cards flexing">
<div className="ui fluid card content">
<div className="card-header">
<h4>
{product.name}
</h4>
<span>
IDR {product.price}
</span>
</div>
<div>
<span>
{this.regexDesc(product.description)}
</span>
<div>
<ul className="furniture-styles">
{product.furniture_style.map(style => {
return (
<li>{style}</li>
)
})}
</ul>
</div>
</div>
<div>
<span>{product.delivery_time} {product.delivery_time > 1 ? "days" : "day"}</span>
</div>
</div>
</div>
)
})
}
</div>
</div>
</div>
);
}
}
export default App;
await this.setState isn't gonna work. If you need to call a function after updating a state, use setState's callback function:
onDropdownChange = (val) => {
this.setState({
checkedFS: val
}, this.filteredFurnitureStyle) // Use callback
}

Got all button updated in reactjs onclick event

A single click on a button updates all the buttons but I want to change the state of that particular clicked button. Please check the image links below and the code.
import React from 'react';
import './MenuCard.css';
class MenuCard extends React.Component {
constructor(props) {
super(props);
this.state = {
showButton: false,
hideButton: true,
aValue: 1,
breads: [],
category: [],
ids: 1,
btnVal: 'Add'
};
}
onKeyCheck = (e) => {
this.state.breads.map(filt => {
if (filt.id === e.target.id) {
console.log(e.target.id + ' and ' + filt.id)
return (this.setState({showButton: !this.state.showButton, hideButton: !this.state.hideButton}));
}
})
}
onShowButton = () => {
this.setState({showButton: !this.state.showButton, hideButton: !this.state.hideButton})
}
onValueIncrease = () => {
this.setState({aValue: this.state.aValue + 1});
}
onValueDecrease = () => {
this.setState({aValue: this.state.aValue - 1});
}
componentDidMount() {
fetch('http://localhost:3000/menu/food_category', {
method: 'get',
headers: {'content-type': 'application/json'}
})
.then(response => response.json())
.then(menudata => {
this.setState({category: menudata.menu_type})
console.log(this.state.category)
})
fetch('http://localhost:3000/menu', {
method: 'get',
headers: {'content-type': 'application/json'}
})
.then(response => response.json())
.then(menudata => {
this.setState({breads: menudata })
})
}
render() {
return (
<div>
{this.state.category.map(types => {
return (<div>
<div className="menu-head">{types}</div>
< div className="container-menu">
{this.state.breads.map((d, id)=> {
if (d.category === types) {
return (
<div>
<div className="content" key={id} id={d.id}>
<div className="items"> {d.item_name}</div>
<div className="prices"> {d.price} Rs.</div>
{this.state.showButton ?
<div>
<button
className="grp-btn-minus"
onClick={this.state.aValue <= 1 ?
() => this.onShowButton() :
() => this.onValueDecrease()}>-
</button>
<input className="grp-btn-text" type="text"
value={this.state.aValue} readOnly/>
<button id={d.id}
className="grp-btn-plus"
onClick={() => this.onValueIncrease()}>+
</button>
</div> :
<button id={d.id} key={id}
onClick={ this.onKeyCheck}
className="add-menu-btn">
add
</button>
}
</div>
</div>
)
}
})}
</div>
</div>)
})}
</div>
)
}
}
export default MenuCard;
This is the first image of multiple rendering of component Add buttons
Here is the problem that all buttons get updated on single click
You're using an array of items but refering to a single, shared value in handlers. De facto you're using a few shared values: showButton, hideButton, aValue), 2/3 unnecessary ;)
First - aValue for each item should be stored in a structure - array or object. It could be an order = {} - object with id-keyed properties with amounts as values like this:
order = {
'masala_id': 1,
'kebab_id' : 2
}
Event handler (for 'add') should check if id for choosen product already exist in order object (as property name) and update amount (+/-) or create new one with 1 value (and remove property when decreased amount = 0).
In practice order should also contain a price - it seams like duplicating data but it will be much easier to count total order value.
order = {
'masala_id': {
'amount': 1,
'price': 20,
},
'kebab_id' : {
'amount': 2,
'price': 180,
}
}
Item doesn't need to be a component but it's much easier to maintain it, keep it readable etc.
This way we can simply pass already ordered amount and conditionally render buttons:
<Product id={d.id}
name={d.item_name}
price={d.price}
amount={order[d.id] ? order[d.id].amount : 0 }
amountHandler={this.changeAmountHandler}
/>
Product should be slightly improved and simplified (f.e. key is needed on top div):
class Product extends React.Component {
render () {
const (id, name, price, amount, amountHandler} = this.props;
const showIncrease = !!amount; // boolean, it also means "don't show add button"
return (
<div key={id} >
<div className="content">
<div className="items">{name}</div>
<div className="prices">{price} Rs.</div>
{showIncrease ?
<div>
<button
className="grp-btn-minus"
onClick={(e) => { amountHandler(e, id, -1) }}
>-</button>
<input className="grp-btn-text" type="text"
value={amount}
readOnly/>
<button
className="grp-btn-plus"
onClick={(e) => { amountHandler(e, id, 1) }}
>+</button>
</div> :
<button
onClick={(e) => { amountHandler(e, id, 1) }}
className="add-menu-btn"
>add</button>
}
</div>
</div>
)}}
This way you can handle all events in one handler, keep entire order state in main component... in case of performance problems just use PureComponent.
It looks like all the buttons are sharing the same state. You could try breaking the button up into its own component, and then move the state that button needs into there. That way when you click a button the state of that one particular button is updated, and not the state of the parent component that contains all the buttons.

Pass item data to a react modal

I have a map that render few items and one of its line is below
<a onClick={()=> this.setState({"openDeleteModal":true)}>Delete</a>
Obviously I want to open a modal when user click the delete, but I have to pass a few things like the name of the item, id of the item to perform the deletion. How can I pass says the name to the modal?
I can bind the obj name to a like this
Delete
Am I on the right track?
When working on React applications, try not to think in terms of passing values to other components, but rather updating state that your components are exposed to.
In your example, assuming your modal component is a child of the same component your list of a tags belongs to, you could set the values you are interested in exposing to the modal on the state, as well as updating the property that signals whether the modal is open or not. For example:
class Container extends React.Component {
constructor(props) {
super(props)
this.state = {
openDeleteModal: false,
activeItemName: '', //state property to hold item name
activeItemId: null, //state property to hold item id
}
}
openModalWithItem(item) {
this.setState({
openDeleteModal: true,
activeItemName: item.name,
activeItemId: item.id
})
}
render() {
let buttonList = this.props.item.map( item => {
return (<button onClick={() => this.openModalWithItem(item)}>{item.name}</button>
});
return (
<div>
{/* Example Modal Component */}
<Modal isOpen={this.state.openDeleteModal}
itemId={this.state.activeItemId}
itemName={this.state.activeItemName}/>
{ buttonList }
</div>
)
}
}
Copying over my answer from How to pass props to a modal
Similar scenario
constructor(props) {
super(props)
this.state = {
isModalOpen: false,
modalProduct: undefined,
}
}
//****************************************************************************/
render() {
return (
<h4> Bag </h4>
{this.state.isModalOpen & (
<Modal
modalProduct={this.state.modalProduct}
closeModal={() => this.setState({ isModalOpen: false, modalProduct: undefined})
deleteProduct={ ... }
/>
)
{bag.products.map((product, index) => (
<div key={index}>
<div>{product.name}</div>
<div>£{product.price}</div>
<div>
<span> Quantity:{product.quantity} </span>
<button onClick={() => this.props.incrementQuantity(product, product.quantity += 1)}> + </button>
<button onClick={() => this.decrementQuantity(product)}> - </button> // <----
</div>
</div>
))}
)
}
//****************************************************************************/
decrementQuantity(product) {
if(product.quantity === 1) {
this.setState({ isModalOpen: true, modalProduct: product })
} else {
this.props.decrementQuantity(product)
}
}
Try this: this is the form which has the button, and is a child component of some other component that passes the handleButtonAction method as props, and the button takes the input data and invokes this parent component method
handleSubmit = (e) => {
e.preventDefault();
const data = e.target.elements.option.value.trim();
if (!data) {
this.setState(() => ({ error: 'Please type data' }));
} else {
this.props.handleButtonAction(data, date);
}
}
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit={this.handleSubmit}>
<input type="text" name="option"/>
<div>
<button>Get data</button>
</div>
</form>
The parent component:
handleButtonAction = (data) => {
axios.get(`http://localhost:3000/someGetMethod/${data}`).then(response => {
const resData = response.data;
this.setState({
openModal: true,
status: response.status,
data: resData
});
}).catch((error) => {
if (error.message.toLowerCase() === 'network error') {
this.setStateWithError(-1, {});
}
else { // not found aka 404
this.setStateWithError(error.response.status, '', {currency, date: ddat});
}
});
}

Categories