React-Bootstrap modal only take last item of map - reactjs

I'm a beginner in coding. I'm creating my graphic designer portfolio. I've put all the portfolio content (thumbnail, client name, description...) into an array called "portfolio" in a JSON file. Each item the array is a different project.
I display a galery of thumbnails, and when I click on a thumbnail, a modal opens with the project details.
I map on my "portfolio" array to display the galery, that works. But when I open the modal, it always display the last item of my array.
import React from 'react';
import Modal from 'react-bootstrap/Modal';
class Portfolio extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
error: null,
isLoaded: false,
items: [],
projectName: "",
show: false
};
this.handleShow = () => {
this.setState({ show: true });
};
this.handleClose = () => {
this.setState({ show: false });
};
}
componentDidMount() {
fetch("https://api.myjson.com/bins/1cbaj5")
.then(res => res.json())
.then(
(result) => {
this.setState({
isLoaded: true,
items: result.portfolio
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const {error, isLoaded, items} = this.state;
if (error) {
return <div>Erreur : {error.message}</div>;
} else if (!isLoaded) {
return <div>Chargement…</div>;
} else {
return (
<div id="portfolio">
<h2>Portfolio</h2>
<ul className="portfolio-list">
{items.map(item => (
<li key={item.client}>
<div className="vignette">
<button onClick={() => this.handleShow()}>
<h4>{item.client}</h4>
<div className="filtre"></div>
<img src={item.vignette} alt={item.titre} />
</button>
<Modal show={this.state.show} onHide={this.handleClose}>
<Modal.Header closeButton>
<h3>{item.client}</h3>
</Modal.Header>
<Modal.Body>
<p>{item.description}</p>
</Modal.Body>
</Modal>
</div>
</li>
))}
</ul>
</div>
);
}
}
}
export default Portfolio;
I would like the modal to display the corresponding project details.
Thank you for your help.

You need to have only 1 modal and pass data dynamically on item click,
<ul className="portfolio-list">
{items.map(item => (
<li key={item.client}>
<div className="vignette">
<button onClick={()=> this.handleShow(item)}> //Pass complete item object here
<h4>{item.client}</h4>
<div className="filtre"></div>
<img src={item.vignette} alt={item.titre} />
</button>
</div>
</li>
))}
<Modal show={this.state.show} onHide={this.handleClose}> //Only one modal
<Modal.Header closeButton>
<h3>{this.state.activeItem.client}</h3>
</Modal.Header>
<Modal.Body>
<p>{this.state.activeItem.description}</p>
</Modal.Body>
</Modal>
</ul>
Now in your handleShow function you can set item to state,
this.handleShow = (item) => {
this.setState({activeItem:item}, ()=> this.setState({ show: true }));
};
Use callback to show modal, which makes sure activeItem have latest clicked item.
Initial state,
this.state = {
error: null,
isLoaded: false,
items: [],
projectName: "",
show: false,
activeItem:'' //new added
};

The previous answer is great. In functional components it is possible to create a function for handling both states and it works perfectly.
export const TeamCollection = () => {
const [modalShow, setModalShow] = useState(false);
const [name, setName] = useState("");
const handleName = (name) => {
setName(name);
setModalShow(true);
};
return (
<Container>
<Row>
{team_data.map((item) => (
<Col key={item.id}>
<div style={{ width: "175px" }}>
<ImageContainer>
<LinkContainer onClick={() => handleName(item.name)}>
<BackgroundImage
style={{ backgroundImage: `url(${item.imageUrl})` }}
/>
</LinkContainer>
</ImageContainer>
<h4>{item.name}</h4>
<p>{item.position}</p>
</div>
</Col>
))}
</Row>
<RouteModal
data={name}
show={modalShow}
onHide={() => setModalShow(false)}
/>
</Container>
);
};
export default TeamCollection;

Related

Use react modal bootstrap to edit items in a map list

My application have two components, one renders a list and the other render the form:
I would like to use the same component to create new categories and also to edit the categories that already exists.
The create categories is working fine already, but to edit, needs to pass the id of the category in the list to the form inside the modal, as i am new into react, i would like some help. Thanks in advance.
The list file is called,
Categories.jsx
import React, { Component } from 'react'
import { Alert, Modal, Button } from "react-bootstrap";
import Datatable from '../../../globalcomponents/datatable/Datatable';
import CategoryForm from './CategoryForm';
const Api = require('../../api/CategoriesApi.js')
class Categories extends Component {
constructor(props) {
super(props)
this.state = {
categories: [],
isLoaded: false,
error: null,
isOpen: false
}
}
openModal = () => this.setState({ isOpen: true });
closeModal = () => this.setState({ isOpen: false });
componentDidMount() {
Api.getCategories()
.then(response => {
const [error, data] = response
if (error) {
this.setState({
isLoaded: true,
categories: [],
error: data
})
} else {
this.setState({
isLoaded: true,
categories: data
})
}
})
}
render() {
const { error, isLoaded, categories } = this.state
if (error) {
return (
<Alert color="danger">
Error: {error}
</Alert>
)
} else if (!isLoaded) {
return (
<Alert color="primary">
Loading...
</Alert>
)
} else {
return (
<>
<Button className="float-right" variant="primary" onClick={this.openModal}>
Adicionar
</Button>
<h4 className="mt-4 mb-4">Categorias de investimentos</h4>
<Datatable>
<table className="table table-striped my-4 w-100">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Url (Slug)</th>
<th></th>
</tr>
</thead>
<tbody>
{categories.map(category => (
<tr key={category.id}>
<td>{category.id}</td>
<td>{category.title}</td>
<td>{category.slug}</td>
<td>
<Button className="float-right mr-2" variant="primary" onClick={this.openModal}>
Modal Edit
</Button>
</td>
</tr>
))}
</tbody>
</table>
</Datatable>
<Modal show={this.state.isOpen} onHide={this.closeModal}>
<Modal.Header closeButton>
<Modal.Title>Adicionar / Editar</Modal.Title>
</Modal.Header>
<Modal.Body>
<CategoryForm />
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.closeModal}>
Close
</Button>
</Modal.Footer>
</Modal>
</>
)
}
}
}
export default Categories
The form file is used to create or edit categories. And it is called:
CategoryForm.jsx
import React, { Component } from 'react'
import { Redirect } from 'react-router'
import { Row, Col, Alert, Button, Form, FormGroup, Label, Input } from 'reactstrap'
const Api = require('../../api/CategoriesApi.js')
class CategoryForm extends Component {
constructor(props) {
super(props)
this.state = {
category: {
id: this.getCategoryId(props),
title: '',
slug: '',
},
redirect: null,
errors: []
}
this.setTitle = this.setTitle.bind(this)
this.setSlug = this.setSlug.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
getCategoryId(props) {
try {
return props.match.params.id
} catch (error) {
return null
}
}
setTitle(event) {
let newVal = event.target.value || ''
this.setFieldState('title', newVal)
}
setSlug(event) {
let newVal = event.target.value || ''
this.setFieldState('slug', newVal)
}
setFieldState(field, newVal) {
this.setState((prevState) => {
let newState = prevState
newState.category[field] = newVal
return newState
})
}
handleSubmit(event) {
event.preventDefault()
let category = {
title: this.state.category.title,
slug: this.state.category.slug,
}
Api.saveCategory(category, this.state.category.id)
.then(response => {
const [error, errors] = response
if (error) {
this.setState({
errors: errors
})
} else {
this.setState({
// reload categories
redirect: '/admin'
})
}
})
}
componentDidMount() {
if (this.state.category.id) {
Api.getCategory(this.state.category.id)
.then(response => {
const [error, data] = response
if (error) {
this.setState({
errors: data
})
} else {
this.setState({
category: data,
errors: []
})
}
})
}
}
render() {
const { redirect, category, errors } = this.state
if (redirect) {
return (
<Redirect to={redirect} />
)
} else {
return (
<>
<Row>
<Col>
{errors.length > 0 &&
<div>
{errors.map((error, index) =>
<Alert color="danger" key={index}>
{error}
</Alert>
)}
</div>
}
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<Label for="title">Title</Label>
<Input type="text" name="title" id="title" value={category.title} placeholder="Enter title" onChange={this.setTitle} />
</FormGroup>
<FormGroup>
<Label for="slug">Slug</Label>
<Input type="text" name="slug" id="slug" value={category.slug} placeholder="Enter slug" onChange={this.setSlug} />
</FormGroup>
<Button color="success">Submit</Button>
</Form>
</Col>
</Row>
</>
)
}
}
}
export default CategoryForm
You are not passing the id in Categories.jsx. either you can set the id in the history state or do pass it by component
prop drill.
setting the state in history Programmatically set params in React Router v4
Or You can do Pass the id to the Component and handle in Component
DidMount Event.
here is the code sandbox link
Categories.jsx
/** Create a id variable in state. **/
class Categories extends Component {
constructor(props) {
super(props);
this.state = {
categories: [],
isLoaded: false,
error: null,
isOpen: false,
--> id: null <--
};
}
/** change the openModal code to something like this. **/
openModal = (id) => {
this.setState( (prev) => {
const state = prev.state;
return { ...state, id: id, isOpen:true };
});
};
/** while Onclick set the id in the state. **/
<Datatable>
<table className="table table-striped my-4 w-100">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Url (Slug)</th>
<th></th>
</tr>
</thead>
<tbody>
{categories.map((category) => (
<tr key={category.id}>
<td>{category.id}</td>
<td>{category.title}</td>
<td>{category.slug}</td>
<td>
<Button
className="float-right mr-2"
variant="primary"
--> onClick={() =>this.openModal(category.id)}
>
Modal Edit
</Button>
</td>
</tr>
))}
</tbody>
</table>
</Datatable>
/** Pass the id prop for CategoryForm Component in Modal body from the state. **/
<Modal show={this.state.isOpen} onHide={this.closeModal} >
<Modal.Header closeButton>
<Modal.Title>Adicionar / Editar</Modal.Title>
</Modal.Header>
<Modal.Body>
--> <CategoryForm id={this.state.id || null} />
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick= {this.closeModal}>
Close
</Button>
</Modal.Footer>
</Moddal>
CategoryForm.jsx
In the componentDidMount conditionally check if there is id variable in props.
componentDidMount() {
**// Check if props.id is available**
if (** this.state.category.id || this.props.id **) {
**const id = this.state.category.id || this.props.id;**
Api.getCategory(id).then((response) => {
const [error, data] = response;
if (error) {
this.setState({
errors: data
});
} else {
alert(id);
this.setState({
category: data,
errors: []
});
}
});
}
}
You can add more state to Categories to keep track of additional data about the modal.
*I have only included the highlights in the code here; lots was left out for brevity.
In Categories.jsx:
constructor(props) {
super(props)
this.state = {
categories: [],
isLoaded: false,
error: null,
isOpen: false,
modalData: null,
}
}
openModal = (modalData) => this.setState({ isOpen: true, modalData });
closeModal = () => this.setState({ isOpen: false, modalData: null });
//'create' dialog button
<Button className="float-right" variant="primary" onClick={e => this.openModal({modalType: 'create'})}>
Adicionar
</Button>
//here are the table rows:
{categories.map(category => (
<tr key={category.id}>
<td>{category.id}</td>
<td>{category.title}</td>
<td>{category.slug}</td>
<td>
<Button className="float-right mr-2" variant="primary" onClick={e => this.openModal({modalType: 'edit', categoryId: category.id})}>
Modal Edit
</Button>
</td>
</tr>
))}
//the modal. pass modalData as a prop:
<Modal show={this.state.isOpen} onHide={this.closeModal}>
<Modal.Header closeButton>
<Modal.Title>Adicionar / Editar</Modal.Title>
</Modal.Header>
<Modal.Body>
<CategoryForm modalData={modalData} />
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.closeModal}>
Close
</Button>
</Modal.Footer>
</Modal>
In CategoryForm.jsx:
//get id from props:
getCategoryId(props) {
return (props.modalData.modalType === 'edit') ? props.modalData.categoryId : false;
//I don't know if this needs to be here:
//try {
// return props.match.params.id
//} catch (error) {
// return null
//}
}
You might have to refactor CategoryForm.jsx. For instance, the categoryId is now a prop, so it doesn't need to be duplicated in state.

if else statement in render?

I'm having some trouble with my code where I want a user to be taken to two specific pages after they click "Sign in" ONLY IF they haven't already gone through that process before. Those two pages, are below:
this.props.history.push('/decision-style');
this.props.history.push('/area-of-expertise');
How it works is, if they have already gone through the process of those two pages, I want them not to go through it again, and just to be redirected to our news page:
this.props.history.push('/news');
If they have gone through the process before, it will already have added their information in MongoDB in the documents "decisionStyle", and "role"
This is /area-of-expertise. Only want them to see this, and /decision-style if they haven't done this before, and therefore, their info isn't in Mongo
I thought I could maybe create an if... else statement in render to do something similar to what I am trying to achieve. However, that wouldn't work, so I have this code below:
class Login extends Component {
constructor(props) {
super(props);
this.state = {fact: null,
badge: null,
isLoaded: false,
error: null,
showRegistration: false,
userAuthFlow: false,
userData: {},
}
}
render() {
const self = this;
console.log(this.state.userData.decisionStyle);
// Create Registration Form
function RegistrationFormModal(props) {
return (
<Modal
{...props}
aria-labelledby="contained-modal-title-vcenter"
centered
>
<Modal.Header>
<Modal.Title id="contained-modal-title-vcenter">
Sign Up
<button className="closeBtn" onClick={self.handleCloseRegistration}></button>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Registration/>
</Modal.Body>
</Modal>
);
}
// Login page
const {error, isLoaded, fact} = this.state;
if (error) {
return (
<div>
Error: {error.messages}
</div>
);
} else if (!isLoaded) {
return (
<Spinner style={{'width': '200px', 'height': '200px', 'font-size': '50px'}} animation="border"/>
);
} else {
return (
<div id="Login">
<Fade top>
<h1 style={{'font-size': '50px'}}>CRCounter
</h1>
<p style={{'font-size': '32px'}}> {JSON.parse(JSON.stringify(fact.fact))}</p>
<p> - {JSON.parse(JSON.stringify(fact.author))} - </p>
</Fade>
<Fade bottom>
<div id="form">
<form>
</form>
<button className="confirmBtn" userAuthFlow={self.state.userData.role !== null && self.state.userData.decisionStyle !== null ? true : false}onClick = {this.handleClick}>Sign in</button>
<a id = "register" onClick={this.handleShowRegistration}>Don't have an account?</a>
<p id = "registerNote" > You won't be able to access most parts of the platform without an account! </p>
</div>
</Fade>
<RegistrationFormModal
show={this.state.showRegistration}
/>
</div>
);
}
}
The code below I created to be mainly responsible for trying to achieve what I want, but it's not working and not sure why as I am a bit of a React noob.. lol
<button className="confirmBtn" userAuthFlow={self.state.userData.role !== null && self.state.userData.decisionStyle !== null ? true : false}onClick = {this.handleClick}>Sign in</button>
The rest of the code (and updated code as well)...
/* eslint-disable require-jsdoc */
import React, {Component} from 'react';
import './Login.css';
import Fade from 'react-reveal/Fade';
import Spinner from 'react-bootstrap/Spinner';
import axios from 'axios';
import Modal from 'react-bootstrap/Modal';
import Registration from './Registration';
class Login extends Component {
constructor(props) {
super(props);
this.state = {fact: null,
badge: null,
error: null,
fact: null,
isLoaded: false,
showRegistration: false,
userAuthFlow: false,
userData: {},
}
}
handleClick = () => {
this.props.history.push('/decision-style');
}
// Registration form
handleShowRegistration = () => {
this.props.history.push('/news');
}
handleCloseRegistration = () => {
this.setState({showRegistration: false});
}
componentDidMount(sub) {
axios.get('/services/getuserdata', {
params: {ID: sub},
})
.then((response) => {
this.setState({userData: response.data});
});
// Get the facts that will be displayed under the CRCounter logo
function getFacts() {
return axios.get('/services/facts');
};
// Get the welcome badge for the user if they signded up successfully for the platform
function getBadge() {
return axios.get('/services/badge', {
params: {
name: 'welcome',
},
});
}
Promise.all([getFacts(), getBadge()])
.then((results) => {
const responseOne = results[0].data;
const responseTwo = results[1].data;
this.setState({
isLoaded: true,
fact: responseOne,
badge: responseTwo,
});
})
.catch((error) => {
this.setState({
isLoaded: true,
fact: {author: '', fact: ''}});
});
}
handleClick() {};
handleCloseRegistration() {};
handleShowRegisteration() {};
render() {
const { error, isLoaded, fact, showRegistration, userData } = this.state;
const flow = userData.role && userData.decisionStyle;
const parse = (str) => JSON.parse(JSON.stringify(str));
// Create Registration Form
const RegistrationFormModal = (props) => {
return (
<Modal
aria-labelledby="contained-modal-title-vcenter"
centered
{...props}
>
<Modal.Header>
<Modal.Title id="contained-modal-title-vcenter">
Sign Up
<button
className="closeBtn"
onClick={this.handleCloseRegistration}
>
Close Button
</button>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Registration />
</Modal.Body>
</Modal>
);
};
// Login page
if (error) {
return (
<div>
Error: {error.messages}
</div>
);
} else if (!isLoaded) {
return (
<Spinner style={{'width': '200px', 'height': '200px', 'font-size': '50px'}} animation="border"/>
);
} else {
return (
<div id="Login">
<Fade top>
<h1 style={{ 'font-size': '50px' }}>CRCounter</h1>
<p style={{ 'font-size': '32px' }}>{parse(fact.fact)}</p>
<p> - {parse(fact.author)} - </p>
</Fade>
<Fade bottom>
<div id="form">
<form>
</form>
<button
className="confirmBtn"
onClick={this.handleClick}
userAuthFlow={flow}
>
Sign in
</button>
<a
id="register"
onClick={this.handleShowRegistration}
>
Don't have an account?
</a>
<p id="registerNote" >
You won't be able to access most parts of the platform without an account!
</p>
</div>
</Fade>
<RegistrationFormModal show={showRegistration} />
</div>
);
}
}}
export default Login;
Rather than checking for falsy values, just check for truthy values instead.
Here, if we have role && decisionStyle then it is true, otherwise false.
The logical AND operator (&&) returns true if both operands are true and returns false otherwise.
self.state.userData.role && self.state.userData.decisionStyle
The logic in your condition is a little off. It should be something like userData.role && userData.decisionStyle;. I also took the liberty of destructuring some of your code, using arrow functions, removing self, and a couple of other things!
UPDATED
class Login extends Component {
constructor(props) {
super(props);
this.state = {
badge: null,
error: null,
fact: null,
isLoaded: false,
showRegistration: false,
userAuthFlow: false, // do you need this?
userData: {},
};
}
componentDidMount() {
// existing code here
}
handleClick = () => {
const { history } = this.props;
const flow = userData.role && userData.decisionStyle;
history.push(flow ? '/news' : '/decision-style');
}
handleShowRegistration = () => {
this.setState({ showRegistration: true });
}
handleCloseRegistration = () => {
this.setState({ showRegistration: false });
}
render() {
const { error, isLoaded, fact, showRegistration, userData } = this.state;
const parse = (str) => JSON.parse(JSON.stringify(str));
// Create Registration Form
const RegistrationFormModal = (props) => {
return (
<Modal
aria-labelledby="contained-modal-title-vcenter"
centered
{...props}
>
<Modal.Header>
<Modal.Title id="contained-modal-title-vcenter">
Sign Up
<button
className="closeBtn"
onClick={this.handleCloseRegistration}
>
Close Button
</button>
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Registration />
</Modal.Body>
</Modal>
);
};
// Login page
if (error) return <div>Error: {error.messages}</div>;
if (!isLoaded) return (
return (
<Spinner
animation='border'
style={{
font-size: '50px',
height: '200px',
width: '200px',
}}
/>
);
);
return (
<div id="Login">
<Fade top>
<h1 style={{ 'font-size': '50px' }}>CRCounter</h1>
<p style={{ 'font-size': '32px' }}>{parse(fact.fact)}</p>
<p> - {parse(fact.author)} - </p>
</Fade>
<Fade bottom>
<div id="form">
<form>
</form>
<button
className="confirmBtn"
onClick={this.handleClick}
>
Sign in
</button>
<a
id="register"
onClick={this.handleShowRegistration}
>
Don't have an account?
</a>
<p id="registerNote" >
You won't be able to access most parts of the platform without an account!
</p>
</div>
</Fade>
<RegistrationFormModal show={showRegistration} />
</div>
);
}
}

Setting conditional onClick behaviour in ReactJS

(This is my first time using React)
I am working on app that filters videos based on the button click and this is the code below
const data = [{ address: 'https://www.youtube.com/watch?v=eIrMbAQSU34', category: 'java' },
{ address: 'https://www.youtube.com/watch?v=RRubcjpTkks', category: 'C#' }];
class User extends React.Component {
constructor(props){
super(props);
this.state={
videos : data,
};
}
render(){
return (
<div className="box">
<div className="buttons-filter">
<button onClick={()=>{
this.setState({videos: data })
}}>
All
</button>
<button id="btnJava" onClick={()=>{
this.setState({videos: data },
()=>{
this.setState({
videos: this.state.videos.filter(item => { return item.category === 'java';})
})
})
}}>
Java
</button>
<button id="btnReact" onClick={()=>{
this.setState({videos: data },
()=>{
this.setState({
videos: this.state.videos.filter(item => {return item.category==='React';})
})
})
}} >
React
</button>
</div>
<div className="content">
<div className="wrapper">
{this.state.videos.map(video => (
<ReactPlayer className="vid" url={video.address} controls={true} height="300" width="350" />
))}
</div>
</div>
<div className='bottom-btn'>
{/* <a className='btn-logout'><Link to="/Login" className='link'>Logout</Link></a> */}
</div>
</div>
);
}
};
export default User;
I was thinking of a way to reduce redundancy in my code, so I want to add an onclick function, semthing like
onClick(){
if(button.id === 'btnJava')
{
this.setState({videos: this.state.videos.filter(item => {
return item.category === 'java';})})
}
else if()
{}
}
Any idea if and how JSX handles a situation like this?
You can have one function to handle all cases based on the button value:
handleClick = (event) => {
const value = event.target.value;
this.setState({ videos: this.state.videos.filter(item => {
return item.category === value
})
})
}
<button value="java" onClick={this.handleClick}>Java</button>
<button value="React" onClick={this.handleClick}>React</button>
The idea is good to abstract the button click into its own function.
onClick passes event as a value to the function and you can get the id or value or which ever attribute from that.
If you are going to use
this.setState({videos: this.state.videos.filter(item => {
return item.category === 'java';
})
The your are going to loose your original video state after filtering.
I would recommend storing the filtered state into a different variable. This way you have the option to reset the filter.
Combining the answer from HermitCrab
this.state={
videos : data,
filtered: data
};
...
handleClick = (event) => {
const value = event.target.value;
this.setState({ filtered: this.state.videos.filter(item => {
return item.category === value
})
})
}
...
<div className="box">
<div className="buttons-filter">
<button value="java" onClick={this.handleClick}>Java</button>
<button value="React" onClick={this.handleClick}>React</button>
</div>
<div className="content">
<div className="wrapper">
{this.state.filtered.map(video => (
<ReactPlayer className="vid" url={video.address} controls={true} height="300" width="350" />
))}
</div>
</div>

React for table rows to bind (this) click event handler

I have a table rows data from server containing images (some other data is removed for simplicity). When images clicked, a modal popup is shown to preview the loaded image to crop and change the image with the cropped one. Everything is work fine.
The problem is, the clicked image on the row should change after the modal submit button is clicked. But I found that the image on the last row is changed.
I know the problem comes from this line but I have no idea how to solve it :
handleSubmit = e => {
e.preventDefault();
console.log(this.state.croppedImageUrl);
this.imagetoCropt.src = this.state.croppedImageUrl;
};
This is the code :
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Button } from "react-bootstrap";
import { Modal } from "react-bootstrap";
import ReactCrop from "react-image-crop";
import "react-image-crop/dist/ReactCrop.css";
import { my_ads } from "./component/AdsFunctions";
export default class Myads extends Component {
constructor() {
super();
this.state = {
myads : {},
modalShow: false,
setShow: false,
setClose: true,
previewImage: "/assets/loader.gif",
src: null,
crop: {
unit: "%",
width: 30,
aspect: 5 / 4
}
};
}
handleImageOnChange = e => {
if (e.target.files && e.target.files.length > 0) {
const reader = new FileReader();
reader.addEventListener("load", () =>
this.setState({
src: reader.result,
modalShow: true
})
);
reader.readAsDataURL(e.target.files[0]);
}
};
onImageLoaded = image => {
this.imageRef = image;
};
onCropComplete = crop => {
this.makeClientCrop(crop);
};
onCropChange = (crop, percentCrop) => {
this.setState({ crop });
};
async makeClientCrop(crop) {
if (this.imageRef && crop.width && crop.height) {
const croppedImageUrl = await this.getCroppedImg(
this.imageRef,
crop,
"newFile.jpeg"
);
this.setState({ croppedImageUrl });
}
}
getCroppedImg(image, crop, fileName) {
const canvas = document.createElement("canvas");
const scaleX = image.naturalWidth / image.width;
const scaleY = image.naturalHeight / image.height;
canvas.width = crop.width;
canvas.height = crop.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(
image,
crop.x * scaleX,
crop.y * scaleY,
crop.width * scaleX,
crop.height * scaleY,
0,
0,
crop.width,
crop.height
);
return new Promise((resolve, reject) => {
canvas.toBlob(blob => {
if (!blob) {
//reject(new Error('Canvas is empty'));
console.error("Canvas is empty");
return;
}
blob.name = fileName;
window.URL.revokeObjectURL(this.fileUrl);
this.fileUrl = window.URL.createObjectURL(blob);
resolve(this.fileUrl);
}, "image/jpeg");
});
}
//---- modal function ------------
handleShow = () => {
this.setState({
modalShow: true
});
};
handleClose = () => {
this.setState({
modalShow: false
});
};
handleImgClick = () => {
this.refs.fileInput.click();
};
handleClickSubmit = () => {
this.refs.btnSubmit.click();
this.setState({
modalShow: false
});
};
//--------- end modal function---
//======== PROBLEM HERE ======================
handleSubmit = e => {
e.preventDefault();
console.log(this.state.croppedImageUrl);
this.imagetoCropt.src = this.state.croppedImageUrl;
};
//=============================================
componentDidMount() {
// AXIOS call
my_ads().then(res => {
this.setState({
myads: res.myads,
});
});
}
render() {
const { crop, croppedImageUrl, src } = this.state;
const show = this.state.modalShow;
// My Ads List from AXIOS call
let myads = this.state.myads;
const RenderMyAds = Object.keys(myads).map((val, index) => (
<tr className="mt-3" key={index}>
<td>
<div className="float-left mr-4">
<div className="card mb-10">
<Link to="#">
<img
className="img-thumbnail img-responsive"
src={myads[val].image}
alt="img"
width={200}
onClick={this.handleImgClick}
ref={ref => (this.imagetoCropt = ref)} <<==== problem here?
/>
</Link>
</div>
</div>
</td>
</tr>
));
return (
<div>
<section>
<div className="container">
<div className="row">
<div className="col-lg-12">
<div className="card">
<div className="card-body">
<div className="table-responsive">
<table className="table table-bordered border-top mb-0">
<tbody>
{RenderMyAds}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<form
encType="multipart/form-data"
acceptCharset="utf-8"
onSubmit={this.handleSubmit}
>
<input
type="file"
className="d-none"
name="userfile"
ref="fileInput"
onChange={this.handleImageOnChange}
/>
<button type="submit" className="d-none" ref="btnSubmit">
Upload Image
</button>
</form>
<Modal size="lg" show={show} onHide={this.handleClose}>
<Modal.Header closeButton>
<Modal.Title>Image Preview</Modal.Title>
</Modal.Header>
<Modal.Body className="text-center"></Modal.Body>
<ReactCrop
src={src}
crop={crop}
onImageLoaded={this.onImageLoaded}
onComplete={this.onCropComplete}
onChange={this.onCropChange}
/>
<img className="d-none" alt="Crop" src={croppedImageUrl} />
<Modal.Footer>
<Button
variant="primary"
className="btn-block"
onClick={this.handleClickSubmit}
>
<i className="fa fa-image mr-2"></i> Upload Image
</Button>
</Modal.Footer>
</Modal>
</div>
);
}
}
You are overwriting the same ref in your map. Consequentially, the last row is the last one to be mapped. You need to instead use an array of refs.
In your contructor, add:
this.imageRefs = [];
Then in your mapping:
const RenderMyAds = Object.keys(myads).map((val, index) => (
<tr className="mt-3" key={index}>
<td>
<div className="float-left mr-4">
<div className="card mb-10">
<Link to="#">
<img
className="img-thumbnail img-responsive"
src={myads[val].image}
alt="img"
width={200}
onClick={this.handleImgClick}
ref={ref => (this.imageRefs[index] = ref)}
/>
</Link>
</div>
</div>
</td>
</tr>
));
This will let you access the correct ref, based on the key assigned to the tr.

Carousel - How do i select carousel item and open modal from the selection

I am using react bootstrap and having carousel . Want to select carousel item and open modal from the selection But unable to do so any help
I would first wrap the Carousel.Item component and add a modal and click handler to the wrapped instance, something like the following maybe
class Item extends Component {
constructor() {
super();
this.state = {
show: false
};
}
render() {
const { caption, title, src, alt, className, modal } = this.props;
return (
<Carousel.Item>
<img
className={className}
src={src}
alt={alt}
onClick={e => this.setState({ show: true })}
/>
<Carousel.Caption>
<h1>
{title}
</h1>
<p>
{caption}
</p>
</Carousel.Caption>
{/* --> modal instance <-- */}
<Modal
show={this.state.show}
onHide={() => this.setState({ show: false })}
>
<Modal.Header closeButton>
<Modal.Title id="contained-modal-title">
{modal.title}
</Modal.Title>
</Modal.Header>
<Modal.Body>
{modal.content}
</Modal.Body>
<Modal.Footer>
<Button onClick={() => this.setState({ show: false })}>
Close
</Button>
</Modal.Footer>
</Modal>
{/* --> end modal instance <-- */}
</Carousel.Item>
);
}
}
next, I would use this in my Carousel component as follows
class MyCarousel extends Component {
constructor() {
super();
this.state = {
items: [
{
caption: "blah 1",
title: "blah 1",
src: "/path/to/image",
alt: "blah 1"
},
{
caption: "blah 2",
title: "blah 2",
src: "/path/to/another/image",
alt: "blah 2"
}
]
};
this.renderItems = this.renderItems.bind(this);
}
renderItems() {
const { items } = this.state;
items.map(item => {
const modal = {
title: item.title,
content: item.caption
};
return <Item key={item.title} {...item} modal={modal} />;
});
}
render() {
return (
<Carousel>
{this.renderItems()}
</Carousel>
);
}
}

Resources