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>
);
}
}
Related
In Short, Let's assume that I have a list of items that I am displaying, and on each item there is a button, like a Quick View button to view extra information about the product in a modal pop-up. Now, everything is working perfectly, except when I click on the button, it brings the information related to the last item of my API/JSON file .. whatever the source of the file is. So, the last item in the Array. when I console.log(index)
it brings the correct index for each card, but it doesn't show the information about each product separately.
import React, { Component } from "react";
import { productsInfo } from "./ProductsData"; // the JS file containing the data
import Modal from "react-modal";
import "./Products.css";
import ProductDetailsPopUp from "./ProductDetailsPopUp";
import axios from "axios";
Modal.setAppElement("#root");
export default class Products extends Component {
state = {
productsData: [],
modalIsOpen: false,
};
OpenModal = () => {
this.setState({ modalIsOpen: true });
};
CloseModal = () => {
this.setState({ modalIsOpen: false });
};
changeProduct = (item, index) => {
this.setState({ showProduct: item });
};
render() {
// const {id, img, title, price, isNew, country, currency} = productsInfo
return (
<div className="ProductsContainer">
{productsInfo.map((item, index) => {
return (
<div className="cardHolder" key={item.id}>
<img src={item.img} alt="Products" />
<button
onClick={() => {
this.OpenModal();
console.log(index);
}}
className="MainProductBtn"
>
QUICK VIEW
</button>
<Modal
key={index}
className="mainModal"
style={{
overlay: {
backgroundColor: "#3333",
opacity: 0.4,
transition: "0.4s",
},
}}
isOpen={this.state.modalIsOpen}
onRequestClose={this.CloseModal}
>
<div className="popupHeader" key={item.id}>
<h3>{item.title}</h3>
<button onClick={this.CloseModal}>×</button>
</div>
<ProductDetailsPopUp />
</Modal>
<p>{item.title}</p>
<small>{`${item.price} USD`}</small>
</div>
);
})}
</div>
);
}
}
I tried including the index in onClick function and also passing it in the state, didn't work
You create Modal for every item inside your map method meaning that it will display productsInfo.length modals and the last one will be on top.
Remote Modal tag from the map method and onClick set the current item at your state, change the display of the dialog to true and read the current item from the state inside your Modal.
e.g.
state = {
productsData: [],
modalIsOpen: false,
currentItem: null
};
OpenModal = item => {
this.setState({ modalIsOpen: true, currentItem: item });
};
onClick={() => {
this.OpenModal(item);
console.log(index);
}}
I'm trying to build a custom modal off this react carousel library
Basically, I'm at the point where the modal is implemented correctly, but I can't seem to find out how to wire the featured image on the modal to always be the same like the one which is currently also being featured in the carousel.
const Modal = ({ modal, items, handleModalFalse, currentIndex, featured }) => {
return (
<div className={modal ? "modal" : "hide"} onClick={handleModalFalse}>
{/* this only features the first image */}
{/* <img src={featured} /> */}
{/* shows every image on modal*/}
{items.map((item, i) => (
<div key={i} className="wrapper">
<img src={item} />
</div>
))}
</div>
);
};
These are all the handlers that power the Carousel.
I'm struggling to wire the featured image correctly (perhaps inside componentDidMount() ?)
class Gallery2 extends React.Component {
state = {
currentIndex: 0,
items: [
"https://homepages.cae.wisc.edu/~ece533/images/airplane.png",
"https://homepages.cae.wisc.edu/~ece533/images/baboon.png",
"https://homepages.cae.wisc.edu/~ece533/images/cat.png",
"https://homepages.cae.wisc.edu/~ece533/images/boat.png"
],
modal: false,
featured: undefined
};
// what should go here instead??
componentDidMount() {
this.setState({ featured: this.state.items[0] });
}
slideTo = i => console.log("clicked") || this.setState({ currentIndex: i });
onSlideChanged = e => this.setState({ currentIndex: e.item });
slideNext = () =>
this.setState({ currentIndex: this.state.currentIndex + 1 });
slidePrev = () =>
this.setState({ currentIndex: this.state.currentIndex - 1 });
handleEnlargeClick = () => {
this.setState({ modal: true });
};
handleModalFalse = () => {
this.setState({ modal: false });
};
render() {
return (
<>
<h3>React Alice Carousel</h3>
{/* MODAL */}
<Modal
modal={this.state.modal}
items={this.state.items}
handleModalFalse={this.handleModalFalse}
currentIndex={this.state.currentIndex}
featured={this.state.featured}
/>
<RenderGallery
items={this.state.items}
responsive={this.responsive}
onSlideChanged={this.onSlideChanged}
currentIndex={this.state.currentIndex}
/>
<button onClick={() => this.handleEnlargeClick()}>
click to enlarge
</button>
<RenderThumbs
items={this.state.items}
slideNext={this.slideNext}
slidePrev={this.slidePrev}
slideTo={this.slideTo}
responsive={this.responsive}
/>
</>
);
}
}
*******
You can see the full Carousel implementation and bug in action here on this code sandbox. Please feel free to fork it.
Right now my Modal only either shows the first image or all of them...
I understand why I currently only get either the first or all of the images, I just can't figure a solution for my issue.
How can I make the featured image on the Modal to be the same as the one currently on the carousel?
The issue in your code is that your modal is looping over all of the items, and displaying them when you click the "enlarge" button. To fix this, you just need to use the selectedIndex with your items variable to get the currently selected item.
Sandbox example: https://codesandbox.io/s/21p19wmpyy
Modal Code:
const Modal = ({ modal, items, handleModalFalse, currentIndex, featured }) => {
return (
<div className={modal ? "modal" : "hide"} onClick={handleModalFalse}>
{/* this only features the first image */}
{/* <img src={featured} /> */}
{/* shows every image on modal*/}
<div className="wrapper">
<img src={items[currentIndex]} />
</div>
</div>
);
};
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.
I've created a page in my react project, in which there are 4 divs(for dropdown) with different classnames and toggled each div to toggle options for each dropdown.
But for that, I have used 4 flag state variables and created 4 handling functions to toggle options for dropdowns.
Showing code for 2 divs:
class CustomTimeView extends Component {
state = {
showFirst: false,
showSecond: false
};
handleFirst = (event) => {
const {showFirst} = this.state;
this.setState({
showFirst: !showFirst
});
}
handleSecond = (event) => {
const {showSecond} = this.state;
this.setState({
showSecond: !showSecond
});
}
render() {
const {showFirst, showSecond} = this.state;
return (
<div>
<div className="first-dropdown" onClick={this.handleFirst}>
First value
</div>
{showFirst &&
<div className="first-option">Option One</div>
}
<div className="first-dropdown" onClick={this.handleFirst}>
Second label
</div>
{showSecond &&
<div className="first-option">Option Two</div>
}
</div>
);
}
}
Is there a better way to solve this problem with a single handling function. I don't want to use redux for that. Thanks in advance.
You could separate things like this. You can re-use the Toggle component and pass the proper handle function.
You could also put this all in one file. Depends on what you will do with your toggles.
const Toggle = ({ id, children, handleToggle }) => (
<div className={`${id}-dropdown`} onClick={() => handleToggle(id)}>
{children}
</div>
);
class App {
state = { first: false, second: false };
handle = id => this.setState({ [id]: !this.state[id] });
render() {
return (
<div>
<Toggle id="first" handleToggle={this.handle}>
First Value
</Toggle>
<Toggle id="second" handleToggle={this.handle}>
Second Value
</Toggle>
</div>
);
}
}
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});
}
});
}