React - one event handler to toggle one of multiple similar elements - javascript

I'm trying to practice React by rebuilding an agency website. I'm working on a section which has staff images, and clicking one of those images opens the relevant staff bio in a modal. The images and the bios are in separate containing divs.
It feels like I should be able to write one event handler that finds and opens the relevant bio depending on which image is clicked (maybe using something like the data attribute?), but I can't figure out what I'd need to add.
Currently I just have a click handler which toggles a piece of 'active' state. That state is then added as a className to toggle whether the modal is showing. Problem of course being that it doesn't differentiate between bios, so they all show regardless which bio is clicked on.
In case it's useful, here is my 'staff bio' component:
const StaffBio = (props) => {
return (
<div className={`teamMemberOverlay ${props.active}`} onClick={props.onClick}>
<div className="teamMemberExpanded">
<h6>{props.name}</h6>
<div className="seperator"></div>
<p className="title">{props.title}</p>
</div>
</div>
);
}
Which is being used like this:
<StaffBio name="NAME HERE" title="TITLE HERE" active={this.state.active} onClick={this.showBio} />
So far I've got the images set up as follows:
<img src={PaulIllustration} className="staffPhoto" onClick={this.showBio} />
And lastly, my event handler:
showBio() {
let toggle = this.state.active === 'is-active' ? '' : 'is-active';
this.setState({active: toggle});
}

class AboutUsSlider extends Component {
constructor(props) {
super(props);
this.showBio = this.showBio.bind(this)
this.next = this.next.bind(this)
this.state = { active: null }
}
next() {
this.refs.slider.slickNext()
}
showBio(id) {
this.setState({active: id});
}
hideBio(){
this.setState({active: null});
}
render() {
var settings = {...}
const people = [{name: 'Paul', title: 'some title'}, {name: 'Ben', title: 'other'}, ...];
return (
<div>
<Slider ref="slider" {...settings}>
<div className="sliderPage">
<h2>Meet our team</h2>
<div className="seperator"></div>
<div className="teamPhotos">
{ // When setting the index, you should use something unique, I'll use the name here.
people.map((p, index) =>
<img key={p.name} src={`${p.name} + 'Illustration'`} className="staffPhoto" onClick={() => this.showBio(index)}) />
}
</div>
<Button BGColor="#009ECC" text="Our process" onClick={this.next} />
</div>
</Slider>
{ this.state.active && <StaffBio name={people[this.state.active]} title={people[this.state.active].title} onClick={this.hideBio}/>
</div>
)
}
EDITED
There are a couple of things you can do.
Each person probably has an id to identify it. So you could modify your showBio to look like this:
showBio(id) {
this.setState({ active: id })
}
This way, you get which person is currently active in your state.
You also need to change your img
<img src={PaulIllustration} className="staffPhoto" onClick={() => this.showBio(PaulId)} />
Where PaulId would be different for each person.
And your StaffBio:
<StaffBio name="NAME HERE" title="TITLE HERE" active={this.state.active == personId} onClick={this.showBio} />
const StaffBio = (props) => {
return (
<div className={`teamMemberOverlay ${props.active ? 'is-active' : ''}`} onClick={props.onClick}>
<div className="teamMemberExpanded">
<h6>{props.name}</h6>
<div className="seperator"></div>
<p className="title">{props.title}</p>
</div>
</div>
);
}

Related

React component function call only updates one component instance

I have a component called RightTab like this
const RightTab = ({ data }) => {
return (
<div className="RightTab flex__container " onClick={data.onClick}>
<img src={data.icon} alt="Dashboard Icon" />
<p className="p__poppins">{data.name}</p>
{data.dropDown === true ? (
<div className="dropdown__icon">
<img src={Assets.Arrow} alt="Arrow" />
</div>
) : (
<div className="nothing"></div>
)}
</div>
);
};
export default RightTab;
The tab has an active state in its CSS like this
.RightTab.active {
background-color: var(--primaryGreen);
}
as you have seen it changes the color when an active class is added. I have an array in the parent component that I pass down to the child component as props. Here is the array
const dataArray = [
{
name: "Dashboard",
icon: Assets.Dashboard,
dropDown: false,
onClick: handleDashBoardClick,
},
{
name: "Inventory",
icon: Assets.Inventory,
dropDown: true,
onClick: handleInventoryClick,
},
{
name: "Reports",
icon: Assets.Reports,
dropDown: true,
onClick: handleReportsClick,
},
];
Here is how I pass the props down.
<RightTab data={dataArray[0]} />
<RightTab data={dataArray[1]} />
<RightTab data={dataArray[2]} />
The data prop passed into the component is an object containing a function call as one of its properties like this. I have an onclick attribute on the child components' main container that is supposed to call the respective function.
The function is what adds the active class to make the background change color. However each time I click on the component it only changes the background of the first occurrence. And as you may have noticed I call the component thrice. No matter which component I click only the first ones background changes.
Here is an example of the function that is on the prop object.
const handleDashBoardClick = () => {
const element = document.querySelector(".RightTab");
element.classList.toggle("active");
};
I don't get what I'm doing wrong. What other approach can I use?
Although you use the component 3 times, it doesn't mean that a change you make in one of the components will be reflected in the other 2, unless you specifically use a state parameter that is passed to all 3 of them.
Also, the way you add the active class is not recommended since you mix react with pure js to handle the CSS class names.
I would recommend having a single click handler that toggles the active class for all n RightTab components:
const MainComponent = () => {
const [classNames, setClassNames] = useState([]);
const handleClick = (name) =>
{
const toggledActiveClass = classNames.indexOf('active') === -1
? classNames.concat(['active'])
: classNames.filter((className) => className !== 'active');
setClassNames(toggledActiveClass);
switch (name) {
case 'Dashboard';
// do something
break;
case 'Inventory':
// ....
break;
}
}
const dataArray = [
{
name: "Dashboard",
icon: Assets.Dashboard,
dropDown: false,
onClick: handleClick.bind(null, 'Dashboard'),
},
{
name: "Inventory",
icon: Assets.Inventory,
dropDown: true,
onClick: handleClick.bind(null, 'Inventory'),
},
{
name: "Reports",
icon: Assets.Reports,
dropDown: true,
onClick: handleClick.bind(null, 'Reports'),
},
];
return (
<>
{dataArray.map((data) =>
<RightTab key={data.name}
data={data}
classNames={classNames} />)}
</>
);
};
const RightTab = ({ data, classNames }) => {
return (
<div className={classNames.concat(['RightTab flex__container']).join(' ')}
onClick={data.onClick}>
<img src={data.icon} alt="Dashboard Icon" />
<p className="p__poppins">{data.name}</p>
{data.dropDown === true ? (
<div className="dropdown__icon">
<img src={Assets.Arrow} alt="Arrow" />
</div>
) : (
<div className="nothing"></div>
)}
</div>
);
};

State into index of array gives error REACT

I'm having issue where when i put my state into index of an array, it gives error.
Here is there line of bug : {WorkData[state].title}
What i need to do is to display the elements of WorkData which is a array with objects : title, content, img math etc...
const WorkData = [
{
id: 0,
title: 'title',
subtext: 'React/Express',
content: 'content',
imgPath: 'imgPath',
},
{
id: 1,
title: 'Little Hero Academy',
subtext: 'React/API REST',
content:
'content,
imgPath: 'imgPath',
},
{
id: 2,
title: 'title',
subtext: 'subtext',
content: 'content',
imgPath: 'imgPath',
},
First, i have a list of cards with a button that contains data:
<div className="work-container flex">
<div className="work-bloc zoom">
<div className="work-hover" />
<div className="work-text">
<div className="text-title">{WorkData[0].title}</div>
<span className="subtext">{WorkData[0].subtext}</span>
</div>
<div
onClick={togglePopup}
data-id={WorkData[0].id}
className="work-showmore button2"
>
Show More
</div>
</div>
</div>
I store the data in state with this function :
const [id, setId] = useState();
const togglePopup = (e) => {
setIsOpen(!isOpen);
setId(e.target.dataset.id);
};
I need that data to get to the popup,
<Popup
togglePopup={togglePopup}
closePopup={closePopup}
isOpen={isOpen}
id={id}
/>
I pass the state to my popup component and try to display the value of id which is displayed
Also,i need to make the popup display of the content of the array that belong to the id (index) i passed in state : workData 1 2 3 etc...
const Popup = ({ closePopup, isOpen, id }) => {
return (
<div className={`popup-container ${isOpen ? 'opened' : 'closed'}`}>
<div className={`popup ${isOpen ? 'opened2' : 'closed2'}`}>
<span className='popup-title'>
{WorkData[id].title}
{id}
</span>
<div className='image-container'>
<img
src='https://i.picsum.photos/id/1060/536/354.jpg?blur=2&hmac=0zJLs1ar00sBbW5Ahd_4zA6pgZqCVavwuHToO6VtcYY'
alt=''
/>
</div>
<p>
Team projet with React and API REST. Our goal was to make an app with
a choosen API and make something out of it. We did a superhero-themed
website with games for children.
<br />
Check on <AiFillGithub className='menuicon' />
</p>
<div onClick={closePopup} className='close-icon button2'>
Show less{' '}
</div>
</div>
</div>
);
};
but i get this error :
**TypeError: _WorkData__WEBPACK_IMPORTED_MODULE_1__.default[id] is undefined**
Thanks in advance for any suggestion
Ok i fixed, it i changed the way of doing it, i mapped my workcard components so i can easily retrieve the id, title, etc, and i didnt change the event onclick to catch the card.id in state, i passed the state to parent, then to the popup, and then into the popup component i just imported the WorkData.js and simply did : WorkData[state-with-id].title etc
See sample of code below
works.js
const togglePopup = (e) => {
setIsOpen(!isOpen);
setPopupId(e.target.dataset.id);
};
const closePopup = () => {
setIsOpen(false);
};
useEffect(() => {
setCards(workData);
}, []);
workcards.js
<div
onClick={togglePopup}
className='work-showmore button2'
data-id={workcard.id}
>
popup.js
import workData from './workData';
<span className='popup-title'>{workData[popupId].title}</span>

image modal with reactjs using javascript

I am tring to open a image modal on clicking the image.I am getting a list of image from the restapi.But my page shows nothing when its rendered.I am using this link as reference: https://www.w3schools.com/howto/tryit.asp?filename=tryhow_css_modal_img
getAllProjectRequirementImageList = () => {
axios.get(this.state.apiUrl+'/api/v1/visitRequirement/getAllByProjectId', {
params: { projectId: this.state.projectId }
}).then((response) => {
console.log("get with list ImageData",response.data.data);
this.setState({ ioImageListing: response.data.data });
}).catch((error)=>{ console.log("error",error); this.setState({ ioImageListing: [] }); });
};
componentDidMount() {
console.log('componentDidMount colling ...');
this.getAllProjectRequirementImageList();
// responsive
var modal = document.getElementById('myModal');
// Get the image and insert it inside the modal - use its "alt" text as a caption
var img = document.getElementById('myImg');
var modalImg = document.getElementById('img01');
var captionText = document.getElementById('caption');
if (img) {
img.onclick = () => {
modal.style.display = 'block';
modalImg.src = this.src;
captionText.innerHTML = this.alt;
};
}
// Get the <span> element that closes the modal
var span = document.getElementsByClassName('close')[0];
span.onclick = () => {
modal.style.display = 'none';
};
}
render() {
return (
<div>
<div>
{this.state.ioImageListing.map((io, key) => {
io.visitRequirementList.map((skill, j) => (
<img
id="myImg"
src={
this.state.apiUrl +
'/api/v1/visitImageRequirementInfo/getImageByImagePathId?imagePath=' +
skill.imagePath
}
alt="Snow"
style={width:"100%";maxWidth:"300px"}
/>
));
})}
</div>
<div id="myModal" className="modal">
<div>
<span className="close">×</span>
<img className="modal-content" id="img01" />
<div id="caption"></div>
</div>
</div>
</div>
);
}
}
and this is the json response i am getting from backend:
{
"data": [
{
"id": "8c83ac41-13b2-4827-96bc-dd251c4cf929",
"visitLocation": "bedroom",
"visitRequirementList": [
{
"imagePath": "visitImageDirectory/332c3b83-82d3-45b6-9660-309ebc3f246d.png",
},
{
"imagePath": "visitImageDirectory/332c3b83-82d3-45b6-9660-309ebc3f246d.png",
}
]
},
{
"id": "05c36c21-adc6-4fa3-9609-b3dea67b9e69",
"visitLocation": "kitchen",
"visitRequirementList": [
{
"imagePath": "visitImageDirectory/7678f04c-22bd-4735-9f7d-8c34db31b714.png"
},
]
}
],
"message": "data Found",
"status": "success"
}
How can i show a list of images in a modal calling data from restapi.My restapi works fine.Any help regarding this would be appreciated.
There are many things you are doing the non-react way in this example. First of all, here's my solution for your problem:
import React, { Component } from 'react';
export default class extends Component {
state = {
showModal: false,
caption: '',
modalSrc: '',
// ...rest of your state
};
componentDidMount() {
this.getAllProjectRequirementImageList();
}
render() {
return (
<div>
<div>
{this.state.ioImageListing.map((io, key) => {
io.visitRequirementList.map((skill, j) => {
const src = `${this.state.apiUrl}/api/v1/visitImageRequirementInfo/getImageByImagePathId?imagePath=${skill.imagePath}`;
const alt = 'Snow'; // or whatever
return (
<img
id="myImg"
src={src}
onClick={() => {
this.setState({ showModal: true, caption: alt, modalSrc: src });
}}
alt={alt}
style={{ width: '100%', maxWidth: '300px' }}
/>
);
});
})}
</div>
<div
id="myModal"
className="modal"
style={{ display: this.state.showModal ? 'block' : 'none' }}
>
<div>
<span className="close" onClick={() => this.setState({ showModal: false })}>
×
</span>
<img className="modal-content" id="img01" src={this.state.modalSrc} />
<div id="caption">
{this.state.caption}
</div>
</div>
</div>
</div>
);
}
}
For me the main takedowns from this are:
In React, if you want to access the DOM, you should never do it with getElementById, querySelector or any other vanilla javascript DOM manipulation methods. Instead, you should use React Ref. Mind you, you shouldn't use refs at all unless it's the only option.
If you want to change style on click, especially stuff like hide and show modal, the easiest way to do so would be by using state and defining a style that depend on that state, just like I did in the modal.
React has its own events. Javascript's regular onclick changes to React's onClick. You can read more about it here.
Hope this code works for you. Let me know how it goes or if you need anything else.
You should read again about rules and conventions when developing applications with React.
First, avoid using DOM manipulation directly like the way you modifying Modal element. Please do it in a React way. Try something like this:
<div id="myModal" className="modal" style={this.state.isShowModal ? "block" : "none"}>
<div>
<span className="close">×</span>
<img className="modal-content" id="img01" src={this.state.displayImgSrc}/>
<div id="caption">{this.state.displayImgCaption}</div>
</div>
</div>
And then move your myImg click event handler outside of componenDidMount. Make it a separated function and use it to directly bind when render from image list.
io.visitRequirementList.map((skill, j) => (
<img
id="myImg"
src={
this.state.apiUrl +
'/api/v1/visitImageRequirementInfo/getImageByImagePathId?imagePath=' +
skill.imagePath
}
onClick={this.imageClick}
alt="Snow"
style={width:"100%";maxWidth:"300px"}
/>
));
Of course, inside imageClick you should implement logic by changing React state instead of direct manipulation.
Hope this can help

Can't access an array inside an Object after passed props to a modal component

i'm building this application with the help of the RestCountries Api to be able to show each country with basic details on a grid, and after a click on each box the app will show a modal with more detailed informations. That's my code so far:
class App extends React.Component{
constructor (props){
super (props);
this.state={
countries : [],
clickedCountry: {},
modalOn : false,
}
}
componentDidMount(){
axios.get(`https://restcountries.eu/rest/v2/all`)
.then(res => {
const data = res.data;
this.setState({
countries : data
})
let countries = this.state.countries
console.log(countries);
})
}
showInfo = (name) => {
this.setState({
clickedCountry : this.state.countries.find(it => it.name===name),
modalOn : true
});
}
closeModal =()=>{
this.setState({
modalOn : false
})
}
render() {
return (
<div className="container">
{this.state.countries.map(country=>
<Country name={country.name}
key={country.name}
population ={country.population}
region={country.region}
capital={country.capital}
flag={country.flag}
showInfo={this.showInfo}
languages={country.languages}
/>
)}
<div style={{display: this.state.modalOn? "block" : "none"}}>
<Modal closeModal={this.closeModal}
name={this.state.clickedCountry.name}
population={this.state.clickedCountry.population}
region={this.state.clickedCountry.region}
capital ={this.state.clickedCountry.capital}
flag={this.state.clickedCountry.flag}
nativeName ={this.state.clickedCountry.nativeName}
subregion={this.state.clickedCountry.subregion}
topLevelDomain={this.state.clickedCountry.topLevelDomain}
languages={this.state.clickedCountry.languages}
/>
</div>
</div>
)
}
}
Modal component :
const Modal = ({closeModal, name, population, region, capital, flag, languages, nativeName, subregion, topLevelDomain, currencies}) => {
return (
<div className="modal">
<div className="modal-content">
<span onClick={closeModal}>x</span>
<div className="img">
<img src={flag}/>
</div>
<p>{name}</p>
<p>Native name: {nativeName}</p>
<p>population: {population}</p>
<p>Region: {region}</p>
<p>Sub Region: {subregion}</p>
<p>Top level domain: {topLevelDomain}</p>
<p>Capital: {capital}</p>
</div>
</div>
)
}
So far for now i have mapped each country and the modal on click is showing more detailed informations. The problem now is the fact i need to access in the api an array that is nested inside an object:
area: 91
gini: null
timezones: ["UTC-04:00"]
borders: []
nativeName: "Anguilla"
numericCode: "660"
currencies: [{…}]
languages: [{…}]
translations: {de: "Anguilla", es: "Anguilla", fr: "Anguilla", ja: "アンギラ", it: "Anguilla", …}
flag: "https://restcountri
I need to access the languages array. Now if i try to map languages inside the country component, i can display the informations. But i want to show the langauges only on the modal component, and if i map the clickedCountry state responsible for the modal i will receive the error that "languages" is undefined. How it comes if is the same object filtered through the find function? Hope i was clear guys, cheers.
I know you understood whats happened!, just add this to Modal component
<ul>
{
languages && languages.map(lan=> {return <li>{lan.name}</li>} )
}
</ul>

Set state for only for self data into map on reactjs

I have a object's array of users and i'm using map to show them, each user have a option buttons that is 'edit' and 'remove' options each option have a onlclick function that set a state to show another view so the code explain itselft
class App extends React.Component {
state = {
edit: false,
remove: false
}
handleEdit = () => {
this.setState({ edit: true })
}
handleRemove = () => {
this.setState({ remove: true })
}
cancelEdit = () => {
this.setState({ edit: false })
}
cancelRemove = () => {
this.setState({ remove: false })
}
renderEditItem = () => {
const {
state: {
edit,
remove
},
cancelEdit,
cancelRemove,
handleEdit,
handleRemove
} = this
if (edit) {
return (
<div>
<span>Edit view</span>
<br/>
<button onClick={cancelEdit}>Cancel</button>
</div>
)
}
if (remove) {
return (
<div>
<span>Remove view</span>
<br/>
<button onClick={cancelRemove}>Cancel</button>
</div>
)
}
return (
<div>
<button onClick={handleEdit}>Edit</button>
<br/>
<button onClick={handleRemove}>Remove</button>
</div>
)
}
renderUsers = () => {
const {
renderEditItem
} = this
const users = [
{
id: 1,
name: 'User1'
},
{
id: 2,
name: 'User-2'
},
{
id: 3,
name: 'User-3'
}
]
return users.map((user) => {
return (
<ul key={user.id}>
<li>
<div>
<span ref='span'>{user.name}</span>
<br/>
{renderEditItem()}
</div>
</li>
</ul>
)
})
}
render () {
return (
<div>
{this.renderUsers()}
</div>
)
}
}
React.render(
<App />,
document.getElementById('app')
);
JSfiddle: Here
The issue is how can you see is, when i click on the button to set the state for edit or remove option, this will show the view for all the items,
and should be only the view that is clicked, i know the state change to true and is the same for all the items but i don't know how to set the state only for one entry any idea?
Thank you in advance.
Your problem is that the edit/remove state is singular and for the entire list. Each item in the list receives the same state here:
if (edit) {
return (
<div>
<span>Edit view</span>
<br/>
<button onClick={cancelEdit}>Cancel</button>
</div>
)
}
The single edit variable from the state is applied to each list item. If you want to individually set the edit state for each item, it will need to be kept track of with that item.
EX:
const users = [
{
id: 1,
name: 'User1',
edit: true
}]
This way each individual item will be able to tell what state it is in individually. User1 item will have an edit mode that is independent of the other users.
Then you can render something like this:
return users.map((user) => {
return (
<ul key={user.id}>
<li>
<div>
<span ref='span'>{user.name}</span>
<br/>
{user.edit ? 'EDIT MODE' : 'NOT EDIT MODE'}
</div>
</li>
</ul>
)
})

Categories