I'm mapping through an array and creating <li> elements for each element of the array. There is also an <img> inside each <li> element which has a display attribute whose value is depending upon the isDownloading state object.
The problem:
Whenever I'm clicking an <li> element the images appear for all of the <li> elements instead of that particular <li> element.
The code:
...
constructor() {
super()
this.state = {data: null, isDownloading: null}
}
componentDidMount() {
...
//array of data from api call
this.setState({data: dataArray})
}
generateList = (data) => {
const listElement = data.map((info, i) => {
return (
<li onClick={() => this.setState({isDownloading: true})} key={i}>Download<img style={{display: this.state.isDownloading ? 'inline-block' : 'none'}} src='loader.png' /></li>
)
}
return listElement
}
render() {
return (
<div>
<ul>
{this.generateList(this.state.data)} //renders 10 li elements
</ul>
</div>
)
}
...
It is because for every img you are doing this :
style={{display: this.state.isDownloading ? 'inline-block' : 'none'}}
You are referencing the same state for each image. If this.state.isDownloading every image's style will be : { 'inline-block'} since this.state.isDownloading is true.
Now one way to display a loader per element is to update your state so that you will know if a download is happening (isDownloading) AND to which img it refers to. So you must track two things :
is downloading ?
which one is downloading ?
You can do that by updating your 'onClick' function, we can for exemple pass the 'key' props as an argument so that we know which li is being downloaded :
<li onClick={() => this.setState({ isDownloading: true, keyItemBeingDownloaded: key})/>
And now you can update your style's condition to this :
this.state.isDownloading && key === this.state.keyItemBeingDownloaded
So now, if isDownloading && if the item's key being downloaded corresponds to the item's key in the loop then display the loader.
Related
I'm trying to get the value from my <li> tag in render but getting the undefined end result. I changed the code according to the suggested answers and now I got the result null.
handleClick= (e) => {
console.log(e.target.getAttribute('device'));
}
render() {
let { deviceData } = this.state;
return (
<React.Fragment>
{deviceData.map((result,i) => (
<li key={i} className="nav-item" onClick= {this.handleClick} device= {result.device_title}>
<a href="#" className="nav-link" >
<i className="nav-icon fas fa-archway" />
<p>
{result.device_title}
</p>
</a>
</li>
))}
</React.Fragment>
)
}
It seems that li tag is not receiving the click event. You can verify this by logging e.target before you log the e.target.name.
In addition to that, li tag does not accept name attribute, instead you should use data-name then access this value through e.target.dataset.name.
Read more about data attributes
Possible Solution
Use the data-name attribute, disable the pointer events on the anchor (a) tag, and set your li to have cursor: "pointer".
export default class App extends React.Component {
state = {
deviceData: [
{
device_title: "Device 1"
},
{
device_title: "Device 2"
}
]
};
handleClick(e) {
console.log(e.target.dataset.name);
}
render() {
let { deviceData } = this.state;
return (
<ul>
{deviceData.map((result, i) => (
<li
key={i}
onClick={this.handleClick}
data-name={result.device_title}
style={{ cursor: "pointer" }}
>
<a href="#" className="nav-link" style={{ pointerEvents: "none" }}>
<i className="nav-icon fas fa-archway" />
<p>{result.device_title}</p>
</a>
</li>
))}
</ul>
);
}
}
You are trying to access the property handleClick on this. However it is not bound to the scope of the constructor. You can either define it with an arrow function which will then be on the scope of the constructor so that it can be callable.
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
You could also directly define it as an arrow function and call the method since arrow functions don't have their own this or arguments binding. Check the Are 'Arrow Functions' and 'Functions' equivalent / exchangeable? question asked here for further information.
handleClick = () => {
console.log('foo');
}
The thing is, name is not a property for the li tag, since it's not meant to be used with it.
It is an attribute, and you can access it in the following way: e.target.getAttribute('name')
However, please read about which tags this should be used on at MDN or W3Schools.
I will really suggest naming the attribute something else though. :)
The target is the element which was clicked on, not the element to which the event handler is bound. Probably the paragraph is the element receiving the click, not the li element.
Use currentTarget to get the element to which the event was bound.
Currently I'm working on Alarm clock app and I want to do it in way so you can add multiple Alarms. Every Alarm is stored in alarms:[] state in App.js file. If alarm is added,I want to display that alarm as a li element under the clock and I want to be able to remove it by clicking on X icon. Also ,when current time === time set for alarm ,Ring.js component renders and alarm starts ringing. When alarm is ringing there is 'turn off' button. How is it possible to delete this specific alarm which is ringing ,from state array after click on turn off button?
I've tried to send removeAlarm function and alarm(which may help in determining which alarm from array delete)as a prop to this component when condition if fulfilled.
function checkTime(){
if(time.alarms[0]){
const alarms = time.alarms.map(function(alarm,i){
if(time.currentHour === alarm.hour && time.currentMinute === alarm.minute && time.currentSecond
>= 0){
return <Ring message={alarm.message} key={i} alarm={alarm} removeAlarm={removeAlarm} />
}
})
return alarms;
}
}
removeAlarm function:
function removeAlarm(alarm){
setTime(prevState => ({
...prevState,
alarms:[...prevState.alarms.filter(el => el !== alarm)]
}))
}
Ring.js file
let message = props.message;
function removeAlarm(alarm){
props.removeAlarm(alarm);
}
function turnOff(e,alarm){
e.preventDefault();
setShowRing(false);
removeAlarm(alarm);
}
<form>
<h3>{message}</h3>
<button onClick={turnOff}>TURN OFF</button>
</form>
I can't figure it out how to do that. I don't know how to use that passed function or how to determine in that onClick function that THIS is that button which has to be send to removeAlarm function ,map thru state and remove that specific one.
Also second problem which I've noticed is with React Spring Transitions. I'm using it in Ring.js,Alarm.js and want to use it also for listing active alarms in ListAlarms.js. I'm using it the exact same way as in first two components but for ListAlarms.js it's not working and I don't undestand why. My goal is to display those active alarms with transitions not just 'blink' there.
Thank you.
CodeSandBox link here
OK some corrections but you have to alter the transitions
First of all you need to filter your list by id, in order to remove correctly the alarm.
function removeAlarm(alarm){
setTime(prevState => ({
...prevState,
alarms:[...prevState.alarms.filter(el => el.id !== alarm.id)]
}))
}
Secondly, I have removed the from property from your transition, since every new object was positioned on top of others. Also, instead of null for the key I used mapping to each item's id.
(item) => item.id
Finally I corrected the order in map function
{listAlarmTransitions.map(({ item, props, key }) => (
So it became
const listAlarmTransitions = useTransition(props.alarms, (item) => item.id, {
enter: { opacity: 1 },
leave: { opacity: 0 }
});
return (
<ul>
{listAlarmTransitions.map(({ item, props, key }) => (
<animated.div key={key} style={props}>
<li
key={item.id}
id={item.id}
onClick={() => {
removeAlarm(item);
}}
>
<FontAwesomeIcon icon={faTimesCircle} className="listIcon" />
<h3>{item.message}</h3>
<span>
{item.hour}:{item.minute}
</span>
</li>
</animated.div>
))}
</ul>
);
Check this sandbox
https://codesandbox.io/s/broken-morning-upqwp
You are filtering out objects/references you should filter out by id.
Your passed alarm argument is an object and your alarms filter array contains objects, find a unique property which you can filter against, by looking at your code, it should be id.
Something like this:
function removeAlarm(alarm){
setTime(prevState => ({
...prevState,
alarms:[...prevState.alarms.filter(el => el.id !== alarm.id)]
}))
}
I have a div mapped so it would render any number of times according to data sent from the database. And on componentDidMount i'm getting an id. I want to change background color of the div which matches the id i'm getting from componentDidMount How can i do it?
my code
componentDidMount() {
alert(this.props.voted_id);
}
render() {
let {contents, submitvote, postId, voted_id} = this.props
return (
<div className="txt_vote_bar_div" id={contents.post_poll_content_id} style={{backgroundColor: this.state.vote_status ? '#0b97c4' : 'white'}}>
<p className="txt_vote_choice" style={{color: this.state.vote_status ? '#FFFFFF' : '#6a6a6a'}}
onClick={() => {
this.handleClick(contents.post_poll_content_id);
}}> {contents.content} </p>
<p className="txt_tot_votes"
style={{color: this.state.vote_status ? '#FFFFFF' : '#6a6a6a'}}> {contents.votes}%
(Votes:)</p>
</div>
);
};
Basically what i want to do is if this.props.voted_id matches contents.post_poll_content_id , i want to change the background color of that div using states.
You can simply get the element and change its style.
componentDidMount() {
let el = document.getElementById(this.props.voted_id);
if(el) {
el.style.backgroundColor = "blue";
}
}
Update
Above approach manipulates DOM directly. It would be wise to let React handle actual DOM manipulations, unless absolute necessity.
To let React take care, you can make changes in JSX as:
<div className= {voted_id===content.post_poll_content_id ? "txt_vote_bar_div active" : "txt_vote_bar_div"} id={content.post_poll_content_id} >
/CSS
.active {
background-color:#0b97c4;
}
Basically this.props.voted_id inside componentDidMount should equal to this.props.voted_id inside render.
How about
style={{backgroundColor:voted_id===contents.post_poll_content_id ? '#0b97c4' : 'white'}}
I have a list of elements created by using an array in react. On user click how can I make the clicked element active (by adding a CSS class) while making the other elements inactive (by removing the active class)?
My rendering of elements looks like this.
{this.props.people.map(function(person, i){
<div className='media' key={i} onClick={state.handleClick.bind(state,i,state.props)}>
<item className="media-body">{person.name}</item>
</div>
}
When the user clicks on one of these elements an active class will be added to the clicked 'media' element making the clicked element 'media active' while removing the 'active' class from the previously clicked element??
constructor(props) {
super(props);
this.state = { activeIndex: 0 };
}
handleClick(index, props) {
// do something with props
// ...
// now update activeIndex
this.setState({ activeIndex: index });
}
render() {
return (
<div>
{
this.props.people.map(function(person, index) {
const className = this.state.activeIndex === index ? 'media active' : 'media';
return (
<div className={className} key={index} onClick={handleClick.bind(this, index, this.props)}>
<item className="media-body">{person.name}</item>
</div>
);
}, this)
}
</div>
);
}
For the sake of clean code I personally would suggest you creating subcomponents to add functionality to mapped elements.
You could create a small subcomponent which simply returns the element which you want to add functionality to just like this :
...
this.state = {
active: false
}
...
return(
<div className=`media ${this.state.active ? 'active' : ''` onClick={()=>{this.setState({active: true})}}>
<item className="media-body">{this.props.name}</item>
</div>
)
...
And in your map function you simply pass the contents as properties:
{this.props.people.map(function(person, i){
<SubComponent key={i} {...person} />
}
That way you stay with clean code in the "root" component and can add complexity to your subcomponent.
In your handleClick method you could store in the component's state the clicked person (looks like the collection is of people). Then set the className conditionally based on, say, the person's id.
You can set the className using something like:
className={this.state.clickedPersonId === i ? 'media media--clicked' : 'media'}
(NB This is using i, the index of the item in the people array; you may want to use something a little more explicit, like the person's real Id.)
So basically what I am doing is iterating through an array of data and making some kind of list. What I want to achieve here is on clicking on a particular list item a css class should get attached.
Iteration to make a list
var sports = allSports.sportList.map((sport) => {
return (
<SportItem icon= {sport.colorIcon} text = {sport.name} onClick={this.handleClick()} key= {sport.id}/>
)
})
A single list item
<div className="display-type icon-pad ">
<div className="icons link">
<img className="sport-icon" src={icon}/>
</div>
<p className="text-center">{text}</p>
</div>
I am not able to figure out what to do with handleClick so that If I click on a particular list it gets highlighted.
If you want to highlight the particular list item it's way better to call the handleClick function on the list item itself, and you can add CSS classes more accurately with this approach,
here is my sample code to implement the single list component
var SingleListItem = React.createClass({
getInitialState: function() {
return {
isClicked: false
};
},
handleClick: function() {
this.setState({
isClicked: true
})
},
render: function() {
var isClicked = this.state.isClicked;
var style = {
'background-color': ''
};
if (isClicked) {
style = {
'background-color': '#D3D3D3'
};
}
return (
<li onClick={this.handleClick} style={style}>{this.props.text}</li>
);
}
});
Keep a separate state variable for every item that can be selected and use classnames library to conditionally manipulate classes as facebook recommends.
Edit: ok, you've mentioned that only 1 element can be selected at a time,it means that we only need to store which one of them was selected (I'm going to use the selected item's id). And also I've noticed a typo in your code, you need to link the function when you declare a component, not call it
<SportItem onClick={this.handleClick} ...
(notice how handleClick no longer contains ()).
And now we're going to pass the element's id along with the event to the handleClick handler using partial application - bind method:
<SportItem onClick={this.handleClick.bind(this,sport.id} ...
And as I said we want to store the selected item's id in the state, so the handleClick could look like:
handleClick(id,event){
this.setState({selectedItemId: id})
...
}
Now we need to pass the selectedItemId to SportItem instances so they're aware of the current selection: <SportItem selectedItemId={selectedItemId} ....Also, don't forget to attach the onClick={this.handleClick} callback to where it needs to be, invoking which is going to trigger the change of the state in the parent:
<div onClick={this.props.onClick} className={classNames('foo', { myClass: this.props.selectedItemId == this.props.key}); // => the div will always have 'foo' class but 'myClass' will be added only if this is the element that's currently selected}>
</div>