React JS - Event Handler in a dynamic list - reactjs

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>)

Related

React - Make an FAQ class toggle on click

I am creating an FAQ with React and I have the questions in strong tags and the answers in p tags. On click of the strong tags I would like to add a class of active to the clicked tag. I am close but there is some sort of scope issue on my toggle function and I'm not sure how to move past it:
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
active: false
};
}
toggleClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
className={this.state.active ? "active" : null}
onClick={this.toggleClass}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
Here's what I have so far
You need to bind toogleClass method to the Questions instance:
Opition one: Using bind
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
active: false
};
this.toggleClass.bind(this)
}
toggleClass() {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
className={this.state.active ? "active" : null}
onClick={this.toggleClass}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
Option 2: Using class property initializer
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
active: false
};
this.toggleClass.bind(this)
}
toggleClass = () => {
const currentState = this.state.active;
this.setState({ active: !currentState });
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
className={this.state.active ? "active" : null}
onClick={this.toggleClass}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
To selected only the active question you can do this:
import React, { Component } from "react";
import "./faq.css";
class Questions extends Component {
constructor(props) {
super(props);
this.state = {
activeQuestion: null
};
}
render() {
let faq = [
{
question: "Lorem",
answer: "Ipsum"
},
{
question: "Dolor",
answer: "Sit"
}
];
return (
<div className="questions">
{faq.map((item, index) => {
return (
<div className="item">
<strong
data-question={item.question}
className={
item.question === this.state.activeQuestion ? "active" : null
}
onClick={() => {
console.log(item.question);
this.setState({ activeQuestion: item.question });
}}
>
{item.question}
</strong>
<p>{item.answer}</p>
</div>
);
})}
</div>
);
}
}
export default Questions;
Your function toggleClass needs to be an arrow function -> toggleClass = () => {...your code here...}. When it's a regular function the outer scope (where this.state is), is not being passed to your function. Without an arrow function, when you refer to this you are referring only to the scope of the toggleClass function, where state does not exist and so is undefined.
Working Code
Also due to setState being async, it's best practice to use current state by referencing it within the setState function like below:
toggleClass = () => {
this.setState(prevState => ({
active: !prevState.active
})
}
When you reference state outside of the setState function then pass it in, it's possible that the state would be different by the time you use it to set it.
i.e. say the currentState you got was true, and then your setState uses current state to set the opposite (False) by the time setState tries to set it, something else might have changed your existing state to False already and you are just setting it to False again (instead of modifying what the current state is at that time which would be False and you want want True). Unlikely in your case, but it is good practice to follow this because you might run into this issue elsewhere

How to loop this state in React?

I try to loop my state with a .map but it didn't work (I don't see my log in the console) ...
{this.state["cards"].map(card => console.log(card.title))}
Please tell me what's the error...
I have my state here (which a see the log in my console) :
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
cards: []
};
fs.readdir(testFolder, (err, files) => {
files.forEach(file => {
this.state.cards.push({
title: "Test1",
pic:
"https://seeklogo.com/images/C/confluence-logo-D9B07137C2-seeklogo.com.png",
content: "Content",
link: "#"
});
});
});
console.log(this.state); // I see this one
}
Here is my console log :
Object {cards: Array[2]}
cards: Array[2]
0: Object
title: "Test1"
pic: "https://seeklogo.com/images/C/confluence-logo-D9B07137C2-seeklogo.com.png"
content: "Content"
link: "#"
1: Object
title: "Test1"
pic: "https://seeklogo.com/images/C/confluence-logo-D9B07137C2-seeklogo.com.png"
content: "Content"
link: "#"
EDIT : Here is my render :
render() {
return (
<div>
<div className="container-fluid text-center">
<h2 className="h2_title">Hi.</h2>
</div>
<div className="main">
<ul className="cards">
{this.state["cards"].map(cards => (
<Card
key={cards.title}
link={cards.link}
title={cards.title}
pic={cards.pic}
/>
))}
</ul>
</div>
</div>
);
}
He doesn't display my cards and if I replace it by console.log it didn't display it in my console
Thanks for helping me !
You need to make your operations inside the componentDidMount and update the state like this:
import React, {Component} from "react";
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
cards: [],
loading: true
};
}
componentDidMount() {
let cards = [];
fs.readdir(testFolder, (err, files) => {
files.forEach(file => {
cards.push({
title: "Test1",
pic:
"https://seeklogo.com/images/C/confluence-logo-D9B07137C2-seeklogo.com.png",
content: "Content",
link: "#"
});
});
this.setState({
cards,
loading: false
})
});
}
render() {
const {cards, loading} = this.state;
if (loading) {
return (
<div>Getting Files, please wait...</div>
)
}
if (cards.length === 0) {
return (
<div>No files found</div>
)
}
return (
<div>
<div className="container-fluid text-center">
<h2 className="h2_title">Hi.</h2>
</div>
<div className="main">
<ul className="cards">
{cards.map(card => (
<Card
key={card.title}
link={card.link}
title={card.title}
pic={card.pic}
/>
))}
</ul>
</div>
</div>
);
}
}
You shouldn't directly change the state like this this.state.cards.push

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>
));
}
}

Cannot change the state of parent component and re-render

im new to React, trying to make some simple 'Chat' app, stuck a bit in some feature.
im trying to make user list, that onClick (on one of the user) it will change the class (to active), and when hitting another user it will set the active class to the new user.
tried a lot of things, managed to make it active, but when hitting another user, the old one & the one receive the 'active' class.
here is my Parent componenet
class Conversations extends React.Component {
constructor(props) {
super(props);
this.loadConversations = this.loadConversations.bind(this);
this.selectChat = this.selectChat.bind(this);
this.state = { count: 0, selected: false, users: [] }
}
selectChat = (token) => {
this.setState({ selected: token });
}
loadConversations = (e) => {
$.get('/inbox/get_conversations', (data) => {
let r = j_response(data);
if (r) {
this.setState({ count: r['count'], users: r['data']});
}
});
}
componentDidMount = () => {
this.loadConversations();
}
render() {
return (
<div>
{this.state.users.map((user) => {
return(<User selectChat={this.selectChat} selected={this.state.selected} key={user.id} {...user} />)
})}
</div>
)
}
here is my Child componenet
class User extends React.Component {
constructor(props) {
super(props);
this.handleSelect = this.handleSelect.bind(this);
this.state = {
token: this.props.token,
selected: this.props.selected,
username: this.props.username
}
}
handleSelect = (e) => {
//this.setState({selected: e.target.dataset.token});
this.props.selectChat(e.target.dataset.token);
}
render() {
return (
<div data-selected={this.props.selected} className={'item p-2 d-flex open-chat ' + (this.props.selected == this.props.token ? 'active' : '')} data-token={this.props.token} onClick={(e) => this.handleSelect(e)}>
<div className="status">
<div className="online" data-toggle="tooltip" data-placement="right" title="Online"></div>
</div>
<div className="username ml-3">
{this.props.username}
</div>
<div className="menu ml-auto">
<i className="mdi mdi-dots-horizontal"></i>
</div>
</div>
)
}
Any help will be great...hope you can explain me why my method didnt work properly.
Thank you.
You can make use of index from map function to make element active.
Initially set selected to 0;
this.state = { count: 0, selected: 0, users: [] }
Then pass index to child component,also make sure you render your User component when you are ready with data by adding a condition.
{this.state.users.length > 0 && this.state.users.map((user,index) => {
return(<User selectChat={this.selectChat} selected={this.state.selected} key={user.id} {...user} index={index} />)
})}
In child component,
<div data-selected={this.props.selected} className={`item p-2 d-flex open-chat ${(this.props.selected === this.props.index ? 'active' : '')}`} data-token={this.props.token} onClick={() => this.handleSelect(this.props.index)}>
...
</div>
handleSelect = (ind) =>{
this.props.selectChat(ind);
}
Simplified Demo using List.

How to reset father component onclick react

I'm doing a menu with a submenu with React. Clicking on the main menu (Example: CRM) creates the submenu (Example Hijo: Argumentarios) but when clicking on the following menu item (Example Padre: Permisos) "Argumentarios" is not deleted and the submenu of "Permisos" is also created.
I need that when pressing in the menu it shows its submenu and eliminates those of the other siblings.
This is a part of my json file
[
{
"Id":"114",
"Padre":"CRM",
"Hijo":"Argumentarios"
},
{
"Id":"115",
"Padre":"CRM",
"Hijo":"Argumentarios"
},
{
"Id":"116",
"Padre":"CRM",
"Hijo":"Argumentarios"
},
{
"Id":"44",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"45",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"47",
"Padre":"Permisos",
"Hijo":"root"
},
{
"Id":"50",
"Padre":"Telefonia",
"Hijo":"Audio"
},
{
"Id":"51",
"Padre":"Telefonia",
"Hijo":"Audio"
}
]
For confidential reasons I can not show the actual file.
In my next code I change the fetch for a fake json url
import React, { Component } from 'react';
import Icon from './Icon';
class Nav extends Component{
constructor(props){
super(props)
this.state = {
menuSubmenu:[],
}
}
componentWillMount(){
fetch('fake/json_menu.php')
.then(response => response.json())
.then(menuSubmenu =>{
menuSubmenu.forEach(datos => {
let data = {
menu:datos.Padre,
submenu:datos.Hijo,
id:datos.Id,
descripcion:datos.Description,
url:datos.URL
}
this.setState({
menuSubmenu:this.state.menuSubmenu.concat([data])
})
})
})
}
render() {
if (this.state.menuSubmenu.length > 0) {
return(
<nav className="nav">
<div className="menu">
<ul className="list">
{this.state.menuSubmenu.map(datos => <Menu key={datos.id} menu={datos.menu} submenu={datos.submenu} descripcion={datos.descripcion} url={datos.url}/>)}
</ul>
<div className="content-bnt">
<button id="desplegar" className='btn btn--rounded'>
<Icon icon="flecha" className='ico-flecha'/>
</button>
</div>
</div>
</nav>
);
}
return (<p>Cargando usuarios...</p>);
}
}
class Menu extends Component{
constructor(props){
super(props)
this.state = {
abrirSubmenu: false,
}
this.submenuClick = this.submenuClick.bind(this);
}
submenuClick() {
this.setState(state => ({
abrirSubmenu: !state.abrirSubmenu
}));
//alert('Click!')
}
render(){
return (
<>
<li className="list__item">
<button title={this.props.menu} id={"mn-" + this.props.menu} className="desplegable" onClick={this.submenuClick.bind(this)}><Icon icon="auriculares" className='ico-auriculares'/>{this.props.menu}</button>
{
this.state.abrirSubmenu
? (
<div id="sb-crm" className="submenu">
<h3 className="nav--title"><Icon icon="descargar" className='ico-descargar'/>{this.props.submenu}</h3>
<ul className="list">
<li className="list__item">
<a href={this.props.url} title={this.props.descripcion}>{this.props.descripcion}</a>
</li>
</ul>
</div>
)
: (
null
)
}
</li>
</>
)
}
}
export default Nav;
I hope to show only one of the menu items at the same time.
¡Thanks a lot!

Resources