Not all touch-events are fired in array.map react - reactjs

I want to add events listeners onTouchStart, Move and End to img tag in array.map function, as a result its catch only one event listener(onTouchStart), but if I set this listeners to div with class="header-added-heroes" all 3 listeners work, I read about binding 'this' to array.map and its catch only onTouchStart, I would be grateful for any information on this question.
{this.props.addedHeroes.map( function(el) {
return (<a name={el.link} key={uniqueId()} className="heroes__link">
<div className="hero"> {console.log(' map this : ', this === that)}
{
<img className="hero__image"
onTouchStart={this.onTouchStart}
onTouchMove={this.handleMove}
onTouchEnd={this.onTouchEnd}
src={el.image}
/>
}
</div>
</a>);
}, this )}
full code:
import React from "react";
import uniqueId from "lodash/uniqueId";
import HeroCounter from "../../images/HeroCounter.svg";
class HeaderAddedHeroes extends React.Component {
state = {
heroes: [1, 2, 3, 4],
showCloseButton: 0
};
constructor(props) {
super(props);
this.onTouchStart = this.onTouchStart.bind(this);
this.onTouchEnd = this.onTouchEnd.bind(this);
this.handleMove = this.handleMove.bind(this);
}
handleMove() {
console.log('moved');
this.setState({ showCloseButton: 1 })
}
onTouchStart() {
console.log('started');
this.setState({ showCloseButton: 2 })
}
onTouchEnd() {
console.log('ended');
this.setState({ showCloseButton: 3 })
}
render() { var that = this;
return (
<header className="header-added-heroes"> { console.log(' this : ', that)}
<div className="header-added-heroes"
onTouchMove={this.handleMove}
onTouchStart={ this.onTouchStart }
onTouchEnd={this.onTouchEnd}>
{ this.state.showCloseButton }
</div>
<div className="heroes">
{this.props.addedHeroes.map( function(el) {
return (<a name={el.link} key={uniqueId()} className="heroes__link">
<div className="hero"> {console.log(' map this : ', this === that)}
{
<img className="hero__image"
onTouchStart={this.onTouchStart}
onTouchMove={this.handleMove}
onTouchEnd={this.onTouchEnd}
src={el.image}
/>
}
</div>
</a>);
}, this )}
</header>
);
}
}

you can always use arrow function.
Solution was: to remove ‘uniqueId()’
{this.props.addedHeroes.map((el, i) => {
return (<a name={el.link} key={i} className="heroes__link">
<div className="hero">
<img className="hero__image"
onTouchStart={this.onTouchStart}
onTouchMove={this.handleMove}
onTouchEnd={this.onTouchEnd}
src={el.image}
/>
</div>
</a>);
}
)}

Related

Cannot read property 'key' of undefined react

Home.js component
import React, { Component } from 'react';
import axios from 'axios';
import styles from './home.module.css';
import TurnArrow from '../../assets/images/turn.svg';
import LoadingGif from '../../assets/images/loading.gif';
import SearchBox from '../SearchBox/SearchBox';
import RepoItem from '../RepoItem/RepoItem';
class Home extends Component {
constructor(props) {
super(props)
this.state = {
repos: [],
inputValue: "",
isEmptySearch: false,
isLoading: false,
per_page: 100,
limit: 10,
total_count: null,
showMore: true,
index: 10,
dataLoaded: false,
reposLength: null
}
this.myRef = React.createRef();
this.updateInputValue = this.updateInputValue.bind(this);
this.fetchRepos = this.fetchRepos.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
}
scrollToMyRef() {
window.scrollTo(0, this.myRef.current.offsetTop);
}
updateInputValue(e) {
this.setState({
inputValue: e.target.value
});
}
fetchRepos() {
if(this.state.inputValue.trim() === "" || this.state.inputValue.trim() === null) {
return this.setState({ isEmptySearch: true});
}
this.setState({
isEmptySearch: false,
isLoading: true
});
axios.get(`https://api.github.com/search/repositories?q=${this.state.inputValue}&per_page=100`)
.then(response => {
this.setState({
total_count: response.data.total_count,
repos: response.data.items,
isLoading: false,
dataLoaded: true,
reposLength: response.data.items.length
})
return this.scrollToMyRef();
})
.catch(err => console.log(err));
}
handleClick() {
this.fetchRepos();
}
handleKeyPress(e) {
if(e.key === "Enter") {
this.fetchRepos();
}
return
}
render() {
let { repos, isEmptySearch, total_count, isLoading } = this.state;
return (
<>
<header className={styles.hero}>
<div className="container">
<div className={styles.innerHero}>
<div>
<h1>Welcome to GIT M<span>EƎ</span>T</h1>
<p>Discover millions of github repositories <br></br>right here, right now.</p>
<p>Start by using the search box on the right.</p>
</div>
<div className={styles.searchBox}>
<SearchBox>
<img src={TurnArrow} alt="arrow pointing to search button" />
<h1>Search Repos</h1>
<input onKeyPress={this.handleKeyPress} onChange={this.updateInputValue} type="text" name="search" id="search" placeholder="E.g. 'ultra amazing html h1 tag...'" autoComplete="off" required />
<button disabled={ isLoading ? true : false } onClick={this.handleClick}>{ isLoading ? <img src={LoadingGif} alt="loading..." /> : "Search" }</button>
{ isEmptySearch ? <p className={styles.errorMessage}>Please enter something first!</p> : "" }
</SearchBox>
</div>
</div>
</div>
</header>
<main>
{this.state.dataLoaded ? <RepoItem ref={this.myRef} total_count={total_count} repos={repos}/> : "" }
<button className={styles.loadMore}>Load More</button>
</main>
</>
);
}
}
export default Home;
RepoList component
import React, { useState, useEffect } from 'react'
import styles from './repo_item.module.css';
import Footer from '../Footer/Footer';
const RepoList = React.forwardRef((props, ref) => {
const [repos, setRepos] = useState([props.repos]);
useEffect(() => {
setRepos(props.repos);
}, [props.repos]);
return (
<>
<div className="container">
<div className={styles.infoWrap}>
<h2>Results</h2>
<p>Found {props.total_count} results</p>
</div>
<div ref={ref} className={styles.repoWrap}>
{repos.length > 0 ? repos.map((item,index) => {
console.log(item);
return (
<div key={index} className={styles.repoItem}>
<div className={styles.userProfile}>
</div>
{ item.name && item.name.length > 20 ? item.name.substring(0,20) + "..." : item.name }
{ item.license.key }
</div>
);
}) : ""}
</div>
</div>
<Footer />
</>
);
})
export default RepoList;
Why... item.license.key doesnt work but item.name works.............help.
I suppes I messsed up with the connection between Home and repo component, But cannot see the error my self. Thats why I am posting it here, maybe someone will notice the problem faster.
Thank you for in advance, I have tried checking for item and its contents but I get same error everytime.
After finding a lot issue is not in code, data you are getting from API e.g https://api.github.com/search/repositories?q=bootstrap&per_page=100
license property is null so you are getting issue.
check null condition
{item.license && item.license.key}
API Call Response:
{
"total_count":276072,
"incomplete_results":false,
"items":[
{
"id":2126244,
"node_id":"MDEwOlJlcG9zaXRvcnkyMTI2MjQ0",
"name":"bootstrap",
.....
"license":{
"key":"mit",
"name":"MIT License",
"spdx_id":"MIT",
"url":"https://api.github.com/licenses/mit",
"node_id":"MDc6TGljZW5zZTEz"
},
......
},
{
"id":5689093,
"node_id":"MDEwOlJlcG9zaXRvcnk1Njg5MDkz",
"name":"android-bootstrap",
.....
"license":null,
.....
}
]
}

Toggle class only on one element, react js

I`m changing class after clicking and it works.
The problem is that, classes change simultaneously in both elements and not in each one separately. Maybe someone could look what I'm doing wrong. Any help will be useful.
import React, { Component } from "react";
class PageContentSupportFaq extends Component {
constructor(props) {
super(props);
this.state = {
isExpanded: false
};
}
handleToggle(e) {
this.setState({
isExpanded: !this.state.isExpanded
});
}
render() {
const { isExpanded } = this.state;
return (
<div className="section__support--faq section__full--gray position-relative">
<div className="container section__faq">
<p className="p--thin text-left">FAQ</p>
<h2 className="section__faq--title overflow-hidden pb-4">Title</h2>
<p className="mb-5">Subtitle</p>
<div className="faq__columns">
<div
onClick={e => this.handleToggle(e)}
className={isExpanded ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>First</strong>
</p>
</div>
<div
onClick={e => this.handleToggle(e)}
className={isExpanded ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>Second</strong>
</p>
</div>
</div>
</div>
</div>
);
}
}
export default PageContentSupportFaq;
Every element must have its seperate expanded value. So we need an array in state.
And here is the code:
import React, { Component } from "react";
class PageContentSupportFaq extends Component {
state = {
items: [
{ id: 1, name: "First", expanded: false },
{ id: 2, name: "Second", expanded: true },
{ id: 3, name: "Third", expanded: false }
]
};
handleToggle = id => {
const updatedItems = this.state.items.map(item => {
if (item.id === id) {
return {
...item,
expanded: !item.expanded
};
} else {
return item;
}
});
this.setState({
items: updatedItems
});
};
render() {
return this.state.items.map(el => (
<div
key={el.id}
onClick={() => this.handleToggle(el.id)}
className={el.expanded ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>{el.name}</strong>
<span> {el.expanded.toString()}</span>
</p>
</div>
));
}
}
export default PageContentSupportFaq;
You can get two state one state for first and another for a second and handle using two function like this
import React, { Component } from 'react';
class PageContentSupportFaq extends Component {
constructor(props) {
super(props)
this.state = {
isExpanded: false,
isExpanded2:false,
}
}
handleToggle(e){
this.setState({
isExpanded: !this.state.isExpanded
})
}
handleToggle2(e){
this.setState({
isExpanded2: !this.state.isExpanded2
})
}
render() {
const {isExpanded,isExpanded2} = this.state;
return (
<div className="section__support--faq section__full--gray position-relative">
<div className="container section__faq">
<p className="p--thin text-left">FAQ</p>
<h2 className="section__faq--title overflow-hidden pb-4">Title</h2>
<p className="mb-5">Subtitle</p>
<div className="faq__columns">
<div onClick={(e) => this.handleToggle(e)} className={isExpanded ? "active" : "dummy-class"}>
<p className="mb-0"><strong>First</strong></p>
</div>
<div onClick={(e) => this.handleToggle2(e)} className={isExpanded2 ? "active" : "dummy-class"}>
<p className="mb-0"><strong>Second</strong></p>
</div>
</div>
</div>
</div>
);
}
}
export default PageContentSupportFaq;
You'll need to track toggled classes in array, that way it will support arbitrary number of components:
// Save elements data into array for easier rendering
const elements = [{ id: 1, name: "First" }, { id: 2, name: "Second" }];
class PageContentSupportFaq extends Component {
constructor(props) {
super(props);
this.state = {
expanded: []
};
}
handleToggle(id) {
this.setState(state => {
if (state.isExpanded.includes(id)) {
return state.isExpanded.filter(elId => elId !== id);
}
return [...state.expanded, id];
});
}
render() {
return elements.map(el => (
<div
key={el.id}
onClick={() => this.handleToggle(el.id)}
className={this.isExpanded(el.id) ? "active" : "dummy-class"}
>
<p className="mb-0">
<strong>{el.name}</strong>
</p>
</div>
));
}
}

Trying to load multiple C3 charts in same react component

I am trying to map over an array and get a chart to appear alongside with each element, but it doesn't seem to work. This same code appeared once correctly, but no other time and I am not sure what I am missing.
I tried to change the id name to where it tags the chart and I did that by adding an index variable, but still not working
import React from 'react'
import c3 from '/c3.min.js'
class SearchedFood extends React.Component {
constructor(props) {
super(props)
this.state = {
}
this.graph = this.graph.bind(this)
}
graph(index) {
c3.generate({
bindto: '#class' + index,
data: {
columns: [
[1, 2, 3], [2, 3,4]
],
type: 'bar'
},
bar: {
width: {
ratio: 0.3
}
}
})}
render () {
return (
<div>
{this.props.foodResults.map((food, i) => {
return (
<div key={i}>
<label>{food.recipe.label}</label>
<img className="card-img-top" src={food.recipe.image} height="250" width="auto"></img>
<a href={food.recipe.url}>{food.recipe.source}</a>
<p>{food.recipe.dietLabels[0]}</p>
<div>
{food.recipe.ingredientLines.map((ingredient, i) => {
return (
<p key={i}>{ingredient}</p>
)
})}
</div>
<p>Calories {Math.floor(food.recipe.calories/food.recipe.yield)}</p>
<div id={`class${i}`}>{this.graph(i)}</div>
</div>
)
})}
</div>
)
}
}
export default SearchedFood
bindto: '#class' + index,/{this.graph...} isn't gonna work. React doesn't render directly/immediately to the DOM.
Looks like you can use elements with bindTo - your best bet is to use a ref
class SearchedFoodRow extends React.Component {
componentDidMount() {
c3.generate({
bindTo: this.element,
...
})
}
render() {
const { food } = this.props
return (
<div>
<label>{food.recipe.label}</label>
<img className="card-img-top" src={food.recipe.image} height="250" width="auto"></img>
<a href={food.recipe.url}>{food.recipe.source}</a>
<p>{food.recipe.dietLabels[0]}</p>
<div>
{food.recipe.ingredientLines.map((ingredient, i) => {
return (
<p key={i}>{ingredient}</p>
)
})}
</div>
<p>Calories {Math.floor(food.recipe.calories/food.recipe.yield)}</p>
<div ref={ element => this.element = element } />
</div>
)
}
}
and then
class SearchFood extends React.Component {
render() {
return (
<div>
{ this.props.foodResults.map((food, i) => <SearchedFoodRow key={i} food={food} />)}
</div>
)
}
}

How to add index value in the list using React.js?

I need to add index value in my data list using React.js. My code is below.
Itemlist.js:
import React, { Component } from "react";
class TodoItems extends Component {
constructor(props, context) {
super(props, context);
this.createTasks = this.createTasks.bind(this);
}
edit(key){
this.props.edit(key);
}
delete(key){
this.props.delete(key);
}
createTasks(item) {
return <li key={item._id}>{item.name}<a href="#" className="button bg_green" onClick={()=>this.edit(item._id)}>Edit</a><a href="#" className="button bg_red" onClick={()=>this.delete(item._id)}>Delete</a></li>
}
render() {
var todoEntries = this.props.entries;
var listItems = todoEntries.map(this.createTasks);
return (
<ul className="theList">
{listItems}
</ul>
);
}
};
export default TodoItems;
Todolist.js:
import React, { Component } from "react";
import TodoItems from "./TodoItems";
import "./TodoList.css";
import ItemService from './ItemService';
import axios from 'axios';
class TodoList extends Component {
constructor(props, context){
super(props, context);
this.state={
items:[]
}
this.addItem=this.addItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
this.editItem = this.editItem.bind(this);
this.ItemService = new ItemService();
}
componentDidMount(){
axios.get('http://localhost:8888/item')
.then(response => {
this.setState({ items: response.data });
})
.catch(function (error) {
console.log(error);
})
}
addItem(e){
e.preventDefault();
if(this.state.editKey){
this.saveEditedText();
return;
}
var itemArray = this.state.items;
if (this.inputElement.value !== '') {
itemArray.unshift({
text:this.inputElement.value,
key:Date.now()
})
this.setState({
items:itemArray
})
//console.log('items',this.state);
this.ItemService.sendData(this.inputElement.value);
this.divRef.insertAdjacentHTML("beforeend", '<p className="textcolor">'+this.inputElement.value+' has added successfully</p>');
this.inputElement.value='';
setTimeout( () => {
this.divRef.querySelector(':last-child').remove();
window.location.reload();
}, 3000);
}
}
saveEditedText(){
let value = this.inputElement.value;
this.setState(prevState => ({
items: prevState.items.map(el => {
if(el.key == prevState.editKey)
return Object.assign({}, el, {text: value});
return el;
}),
editKey: ''
}));
this.divRef.insertAdjacentHTML("beforeend", '<p className="textcolor">'+this.inputElement.value+' has updated successfully</p>');
this.inputElement.value='';
setTimeout( () => {
this.divRef.querySelector(':last-child').remove();
}, 3000);
}
render() {
return (
<div className="todoListMain">
<div className="header" id="parentDiv">
<div className="pageHeading" dangerouslySetInnerHTML={{ __html: "Todo Demo Application" }}></div>
<div className="wrapper">
<div ref={divEl => {
this.divRef = divEl;
}}></div>
<form onSubmit={this.addItem}>
<input ref={(a)=>this.inputElement=a} placeholder="enter task">
</input>
<button type="submit">{this.state.editKey? "Update": "Add"}</button>
</form>
<TodoItems entries={this.state.items} delete={this.deleteItem} edit={this.editItem}/>
</div>
</div>
</div>
);
}
}
export default TodoList;
Here after adding the data into db, the added data are shown in the list. Here I need to display the index value for each row means 1 - item1 like this.
You can do this by :
class TodoItems extends Component {
constructor(props, context) {
super(props, context);
this.createTasks = this.createTasks.bind(this);
}
edit(key){
this.props.edit(key);
}
delete(key){
this.props.delete(key);
}
render() {
var todoEntries = this.props.entries;
return (
<ul className="theList">
{todoEntries.map(this.createTasks, this)}
</ul>
);
}
createTasks(item, index) {
return (
<li key={item._id}>
{index} - {item.name}
<a href="#" className="button bg_green" onClick={()=>this.edit(item._id)}>Edit</a><a href="#" className="button bg_red" onClick={()=>this.delete(item._id)}>Delete</a>
</li>
)
}
};
If you mean to display index in ItemList component map has an overload which has paramater index that represents current index of element in array being processed.
See more in docs
So make createTasks(item, index){ } and then you will have access to index of the element.

React JS - Event Handler in a dynamic list

I'm bringing a API s' content based on a dynamic list and I'm trying to apply a mouserEnter on each li. The event results by toggling content in the each list item. The event is working but it is toggling content in all the list items all at once, but I want it to toggle only the content that matches with the list item that is receiving the mouseEnter.
import _ from 'lodash';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
export default class Dribbble extends React.Component {
constructor(props) {
super(props);
this.state = {
work: [],
hover: false
};
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
}
handleMouseEnter(){
this.setState({ hover: true })
}
handleMouseLeave(){
this.setState({ hover: false })
}
componentDidMount() {
this.ShotList();
}
ShotList() {
return $.getJSON('https://api.dribbble.com/v1/shots?per_page=3&access_token=41ff524ebca5e8d0bf5d6f9f2c611c1b0d224a1975ce37579326872c1e7900b4&callback=?')
.then((resp) => {
this.setState({ work: resp.data.reverse() });
});
}
render() {
const works = this.state.work.map((val, i) => {
return <li key={i} className="box"
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
{!this.state.hover ?
<div>
<img className="cover" src={val.images.normal} />
<div className="bar">
<h2>{val.title}</h2>
<span>{val.views_count}</span>
<i className="fa fa-eye fa-2x" aria-hidden="true"></i>
</div>
</div>
: null}
{this.state.hover ?
<div>
<h3>{val.user.name}</h3>
<img className="avatar img-circle" src={val.user.avatar_url}/>
<p>{val.description}</p>
</div>
:
null
}
</li>
});
return <ul>{works}</ul>
}
}
Here is my code:
There are couple of issues in your example, firstly as #aherriot states you should move the ul outside the map.
Next i would set this.state.hover to be the id of the item being hovered over on onMouseEnter.
The below snippet shows a basic example of this working that should be easy enough to adapt to your code.
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
items: [{id: 1, name: 'Fred'}, {id: 2, name: 'Albert'}, {id: 3, name: 'Jane'}],
hover: false,
}
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.renderItem = this.renderItem.bind(this);
}
handleMouseEnter(id){
console.log(`handleMouseEnter this.setState({ hover: ${id} })`);
this.setState({ hover: id })
}
handleMouseLeave(){
console.log('handleMouseLeave this.setState({ hover: false })');
this.setState({ hover: false })
}
renderItem(item, index) {
let content = [];
content.push(
<span>ID: {item.id}, Name: {item.name}</span>
);
if(this.state.hover === item.id) {
console.log('display " - hovering" for item id: ' + item.id);
content.push(
<span> - hovering</span>
);
}
return (
<li key={item.id}
onMouseEnter={() => this.handleMouseEnter(item.id)}
onMouseLeave={this.handleMouseLeave}
>
{content}
</li>
)
}
render() {
return <ul>
{this.state.items.map(this.renderItem)}
</ul>
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script>
<div id="root"></div>
Maybe you should move the <ul> tag outside of this.state.work.map You only want one <ul> to show up, not one for each element.
You can place it at the bottom inside your div tag instead: return (<div><ul>{works}</ul></div>)

Resources