How to update the state into a function in react - reactjs

I am creating an application with tabs. When clicking on any of the tabs you should update the class index-big by modifying the state of this.state.addActiveTabs but this does not happen.
I get the impression that this.state.addActiveTabs comes empty and that's why the class never changes, but I do not understand why.
Edit: I think the problem comes from that openTabs is a function with a push that creates the elements and can not update the content of a push of a function.
openTabs is called from the brother component of Tabs. It is called when clicking on a menu item. When it is called is when it creates a tab and at the same time a div/iframe
The problem is that the .push creates the html and after creating it within a function I am not able to access it. How could I do it? I need to change only one class, not to update the whole element or anything.
class App extends Component {
constructor(props, context){
super(props, context);
["openTabs", "removeTab", "activeTabs",].forEach((method) => {
this[method] = this[method].bind(this);
});
this.displayData = [];
this.state = {
navigation: {
menu: [],
},
tabs:{
tabsLi: [],
},
divIframe:{
tabsDivIframe: [],
},
tabsiframe: '',
showtabs: true,
showdata: this.displayData,
postVal: "",
addActiveTabs: "",
};
}
openTabs(e, url, iframe, trdtitle){
//Evitar apertura automatica href
e.preventDefault();
//Cambiar la primera letra a mayuscula y las demas a minusculas
function firstUppercase(string){
return string.charAt(0).toUpperCase() + string.slice(1);
}
trdtitle = firstUppercase(trdtitle.toLowerCase());
url = url.toLowerCase();
//Cambiar el estado
this.setState({
showtabs: false,
})
//Creacion de las tabs + mostrar componentes
if (this.state.tabs.tabsLi.includes(trdtitle) === false){
if(iframe === 'si'){
console.log(this.state.addActiveTabs);
this.displayData.push(<div key={trdtitle.replace(/ /g, "")} id={"myTab"+trdtitle.replace(/ /g, "")} className={this.state.addIndexTabs === trdtitle ? ' index-big' : ''}><iframe title={"iframe"+trdtitle} className="iframetab" src={url}></iframe></div>);
}
else{
this.displayData.push(<div key={trdtitle.replace(/ /g, "")} id={"myTab"+trdtitle.replace(/ /g, "")} className={this.state.addIndexTabs === trdtitle ? ' index-big' : ''}><div className="iframetab">{url}</div></div>);
}
this.setState({
tabs: { tabsLi:[...new Set(this.state.tabs.tabsLi),trdtitle].filter(function(el) { return el; })},
showdata : this.displayData,
postVal : trdtitle,
})
}
}
activeTabs(value){
this.setState({
addActiveTabs: value,
})
return () => this.setState({
addIndexTabs: value,
});
}
render(){
return (
<>
<Tabs
navigation={this.state.navigation}
textvalue={this.state.textvalue}
showtabs={this.state.showtabs}
tabs={this.state.tabs}
tabsLi={this.state.tabs.tabsLi}
divIframe={this.state.divIframe}
tabsDivIframe={this.state.divIframe.tabsDivIframe}
tabsiframe={this.state.tabsiframe}
showdata={this.state.showdata}
addActiveTabs={this.state.addActiveTabs}
openTabs={this.openTabs}
removeTab={this.removeTab}
displayData={this.displayData}
activeTabs={this.activeTabs}
/>
</>
)
}
}
class Tabs extends Component {
constructor(props, context){
super(props, context);
["showCloseTabs", "hideCloseTabs"].forEach((method) => {
this[method] = this[method].bind(this);
});
this.state = {
closeTabs: false,
};
}
showCloseTabs(index, value){
this.setState({
closeTabs : true,
valueTabs: value,
})
}
hideCloseTabs(){
this.setState({
closeTabs: false,
})
}
render(){
return(
<div id="content-tabs" className="tabs">
{( this.props.showtabs)
? (
<>
<div className="waiting-leads">
<p>Parece que todavía no hay ningún lead...</p>
<h3>¡Ánimo, ya llega!</h3>
<img src={imgDinosaurio} alt="Dinosaurio"></img>
</div>
</>
) : (
<>
<ul id="resizable" className="content" >
<LiTabs
tabsLi={this.props.tabs.tabsLi}
closeTabs={this.state.closeTabs}
addActiveTabs={this.props.addActiveTabs}
valueTabs={this.state.valueTabs}
removeTab={this.props.removeTab}
activeTabs={this.props.activeTabs}
showCloseTabs={this.showCloseTabs}
hideCloseTabs={this.hideCloseTabs}
/>
</ul>
<DivAndIframe
tabsDivIframe={this.props.divIframe.tabsDivIframe}
tabsiframe={this.props.tabsiframe}
displayData={this.props.displayData}
/>
</>
)}
</div>
);
}
}
class LiTabs extends Component{
render(){
return(
<>
{this.props.tabsLi.map((value, index) =>
<li key={index}
onClick={(e) => this.props.activeTabs(value)}
onMouseEnter={(e) => this.props.showCloseTabs(e, value)}
onMouseLeave={(e) => this.props.hideCloseTabs(e, value)}
className={this.props.addActiveTabs === value ? ' active' : ''}>
<span>{value}</span>
<div onClick={this.props.removeTab.bind(this, value, index)} >
{this.props.closeTabs && this.props.valueTabs === value &&(
<Icon icon="cerrar" className='ico-cerrar'/>
)}
</div>
</li>
)}
</>
);
}
}
class DivAndIframe extends Component{
render(){
return(
<div className="content-modules">
{this.props.displayData}
</div>
);
}
}

Make a use of closure to hold the value of your tab value in the onClick handler like below:
activeTabs(value){
return () => this.setState({
addActiveTabs: value,
});
}
and then on your onClick, change it to below:
<li key={index}
onClick={(e) => this.props.activeTabs(value)}
className={this.props.addActiveTabs === value ? ' active' : ''}>
<span>{value}</span>
</li>

Related

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.

Cannot read property 'tabsDivIframe' of undefined - React

I am creating an application with tabs and divs to show the iframes or divs associated with the tabs. I have a navigation menu that works perfectly, when you click on one of the menu items you create a new tab and at the same time you should create a div / iframe (as applicable). The creation of the div is failing in my DivAndIframe class, it gives this error Can not read property 'tabsDivIframe' of undefined when I try to paint <DivAndIframe tabsDivIframe {this.props.divIframe.tabsDivIframe} />. It does not make sense because in my class App is an array with content that does not throw any errors.
class App extends Component {
constructor(props, context){
super(props, context);
["openTabs"].forEach((method) => {
this[method] = this[method].bind(this);
});
this.state = {
tabs:{
tabsLi: [],
},
divIframe:{
tabsDivIframe: [],
},
showtabs: true,
}
}
openTabs(e, url, iframe, trdtitle){
e.preventDefault();
//Change the state
this.setState({
showtabs: false,
})
//Creating tabs + iframe/div
if (this.state.tabs.tabsLi.includes(trdtitle) === false){
this.setState({
tabs: { tabsLi:[...new Set(this.state.tabs.tabsLi),trdtitle].filter(function(el) { return el; })},
divIframe: { tabsDivIframe:[...new Set(this.state.divIframe.tabsDivIframe),url].filter(function(el) { return el; })},
}, () => {
//this.state.divIframe.tabsDivIframe is an array
console.log(this.state.tabs.tabsLi);console.log(this.state.divIframe.tabsDivIframe)
})
}
}
render(){
return (
<>
<section className='section'>
<Tabs
navigation={this.state.navigation}
textvalue={this.state.textvalue}
showtabs={this.state.showtabs}
tabs={this.state.tabs}
tabsLi={this.state.tabs.tabsLi}
tabsDivIframe={this.state.divIframe.tabsDivIframe}
openTabs={this.openTabs}
removeTab={this.removeTab}
/>
</section>
</>
)
}
}
class Tabs extends Component {
render(){
return(
<div id="content-tabs" className="tabs">
{( this.props.showtabs)
? (
<>
<div className="waiting-leads">
<p>Parece que todavía no hay ningún lead...</p>
<h3>¡Ánimo, ya llega!</h3>
<img src={imgDinosaurio} alt="Dinosaurio"></img>
</div>
</>
) : (
<>
<ul id="resizable" className="content" >
<LiTabs
tabsLi={this.props.tabs.tabsLi}
removeTab={this.props.removeTab}
/>
</ul>
<DivAndIframe
tabsDivIframe={this.props.divIframe.tabsDivIframe}
/>
</>
)}
</div>
);
}
}
class LiTabs extends Component{
render(){
return(
<>
{this.props.tabsLi.map((value, index) =>
<li key={index}>
<span>{value}</span>
</li>
)}
</>
);
}
}
class DivAndIframe extends Component{
render(){
return(
<>
{this.props.tabsDivIframe.map((url, index) =>
<div key={index}>
<span>Test {url}</span>
</div>
)}
</>
);
}
}
I do not understand why DivAndIframe does not work when it is exactly the same as LiTabs
I think you have a typo.
When rendering Tabs, in App, you pass the props:
<Tabs
navigation={this.state.navigation}
textvalue={this.state.textvalue}
showtabs={this.state.showtabs}
tabs={this.state.tabs}
tabsLi={this.state.tabs.tabsLi}
tabsDivIframe={this.state.divIframe.tabsDivIframe}
openTabs={this.openTabs}
removeTab={this.removeTab}
/>
And inside Tabs you have:
<DivAndIframe
tabsDivIframe={this.props.divIframe.tabsDivIframe}
/>
You aren't passing divIframe to Tabs and that is why you are getting Can not read property 'tabsDivIframe' of undefined. this.props.divIframe is undefined.
Maybe it should be other name?
Like this.props.tabsDivIframe ?

onClick event on an array element javascript React

I need to do a collapsible section with a list of disciplines. The disciplines are stored in an array. I wrote an onClick event but except one discipline which I clicked, I get all of them slid down. How can I apply the event to each element, so I can decide which one will be slide down?
export default class Predictions extends React.Component {
constructor(props){
super(props);
this.handleClick = this.handleClick.bind(this);
this.state={
display: 'block',
};
}
handleClick(e) {
this.setState({
display: this.state.display === 'none' ? 'block' : 'none',
});
console.log('click', e);
};
render() {
return (
<section className="l-section c-predictions" >
<h2 className="header" >Predictions</h2>
<div className="content">
{this.props.disciplines.map((discipline, index) => {
return (
<div onClick={event => this.handleClick(discipline.id, event)} key={discipline.name} className="c-discipline">
<span className="name">{discipline.name}</span> - <span className="score">{disciplineScore(this.props.athlete.skillset, discipline.requirements)}</span>
<div style={this.state} className="element">
<p>{discipline.tags !== undefined ? discipline.tags.toString().replace(',', ', ') : ''}</p>
<p className="isIndividual">{discipline.isIndividual===true ? "Individual sport" : "Team sport"}</p>
<img src={discipline.photo}/>
</div>
</div>
)
})}
</div>
</section>
)
}
}
You need a way to identify which element has been clicked.
Here's an example:
export default class App extends React.Component {
state = {
opened: true,
selected: ''
};
toggleHidden = key => {
this.setState({ opened: !this.state.opened, selected: key });
};
render() {
return (
<div>
{arr.map((s, i) => (
<div key={i}>
<p>{s}</p>
<button onClick={() => this.toggleHidden(i)}>Toggle</button>
{!this.state.opened && this.state.selected === i && <h1>{s}</h1>}
</div>
))}
</div>
);
}
}

Highlight item onClick - React.js

Add underscore to category-item onClick and remove underscore for any other item. Found some answers on how to do this with only two components, a "item-container-component" and "item-components". But i have three components involved. This is what I hope to achieve:
Archive-component (mother component):
class Archive extends React.Component {
constructor(props){
super(props);
this.state = {
products: [],
category: "",
activeIndex: 0
}
this.filterHandler = this.filterHandler.bind(this);
}
filterHandler(tag, index){
console.log('INDEX: ' + index);
this.setState({
category: tag,
activeIndex: index
})
}
componentDidMount(){
const myInit = {
method: "GET",
headers: {
"Content-Type": "application/json"
}
};
fetch("/getProducts", myInit)
.then((res) => {
return res.json();
})
.then((data) => {
this.setState({products:data});
})
.catch(function(err) {
console.log('ERROR!!! ' + err.message);
});
}
render() {
return (
<div>
<Menu />
<div className="archive-container">
<div className="archive-wrapper">
<CategoryContainer
filterHandler={this.filterHandler}
products={this.state.products}
activeIndex={this.state.activeIndex}
/>
<br/><br/>
<ProductContainer
products={this.state.category.length
? this.state.products.filter((prod) => prod.category === this.state.category)
: this.state.products.filter((prod) => prod.category === 'Paint')
}
/>
</div>
</div>
<Footer />
</div>
);
};
};
Category-container component:
class CategoryContainer extends Component {
render(){
const categories = [...new Set(this.props.products.map(cat => cat.category))];
return (
<div>
<ul className="filterList">{
categories.map((cat, index) =>
<CategoryItem
key={index}
index={index}
category={cat}
active={index === this.props.activeIndex}
handleClick={() => this.props.filterHandler(cat, index)}
/>
)
}</ul>
</div>
);
};
};
Category-item component:
class CategoryItem extends Component {
render(){
return (
<div>
<li
className={this.props.active ? 'active' : 'category'}
onClick={this.props.handleClick}
>
{this.props.category}
</li>
</div>
);
};
};
Yelp!
M
Suppose you have a li tag for which you want to change the color of.
you could probably try something like this.
<li id="colorChangeOnClick" class="redColor" onclick={this.onClickFunction()}></li>
Then in your react class you can have the on click function with parameters e:
onClick(e) {
//This would give you all the field of the target
console.log(e.target.elements);
// you can do all sorts of Css change by this way
e.target.element.class="newGreenColor";
}
Also make sure to make a state or a prop change otherwise the page would not render again.

reactjs map and css class toggle

<div>
{this.props.data.map((res, index) => {
return (<div key={index}>
<div>
<span>{response.testData}</span>
<a key={index} onClick={() => this.showExtraLine(index)}><span className={`btn-green ${this.state.showExtraLine ? 'active' : ''}`}></span></a>
{ this.state.showExtraLine ? <span>
{res.abc[0].def}
</span> : '' }
</div>
</div>
);
})}
</div>
showExtraLine(e){
this.setState({
showExtraLine: !this.state. showExtraLine,
});
}
Need to toggle the {res.abc[0].def} part on click of anchor - toggle works, but not sure how to handle toggling only the corresponding span - right now it is hiding all the rows..how to handle css toggle when using .map?
I think the problem is in your state variable, you are using a single state variable and printing the <span> on the basis of state of that variable.
Instead of that use an array in state showExtraLine = [],
in showExtraLine() function you are passing index, use that index to toggle only that element.
Try this it should work:
{this.props.data.map((res, index) => {
return (<div key={index}>
<div>
<span>{response.testData}</span>
<a key={index} onClick={() => this.showExtraLine(index)}><span className={`btn-green ${!this.state.showExtraLine[index] ? 'active' : ''}`}></span></a>
{ !this.state.showExtraLine[index] ?
<span>{res.abc[0].def}</span>
: '' }
</div>
</div>
);
})}
showExtraLine(index){
let showExtraLine = this.state.showExtraLine.slice(0);
showExtraLine[index] = !showExtraLine[index];
this.setState({
showExtraLine: showExtraLine,
});
}
Right now you are maintaining the state for all of the mapped elements in your component, so they all reference the same value. You should instead create a component that will be used to render each of your mapped elements individually with their own state.
class Parent extends React.Component {
render() {
return (
<div>
{this.props.data.map((res, index) => <Child key={index} {...res} />)}
</div>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
showExtraLine: false
};
this.showExtraLine = this.showExtraLine.bind(this);
}
render() {
return (
<div>
<div>
<span>{this.props.testData}</span>
<a key={index} onClick={this.showExtraLine}>
<span className={`btn-green ${this.state.showExtraLine ? 'active' : ''}`}></span>
</a>
{ this.state.showExtraLine ? <span>{this.props.abc[0].def}</span> : '' }
</div>
</div>
);
}
showExtraLine(e){
this.setState({
showExtraLine: !this.state.showExtraLine
});
}
}

Resources