Need Change active class in tab ( react) - javascript

If I click Second page,after reload page, all tabs get class CURRENT, How to fix this? How to disable current class on first TAB ?
If i remove activeClassName="current", After Reloading current class switch to first tab, but I saw second tab content
import React from 'react'
import { Link, browserHistory,IndexLink } from 'react-router'
class Tabs extends React.Component{
constructor(props) {
super(props);
this.state = {
index: ''
};
this.onclick = this.onclick.bind(this);
}
onclick(index) {
this.setState({index});
}
getListItem(){
let numbers = this.props.menuitems;
let listItems = numbers.map((item,index) =>
<li
onClick={this.onclick.bind(this, index)} key={index}>
<Link to={item.link} activeClassName="current"
className={index == this.state.index? "tab-link current" : "tab-link"}>{item.linkName}</Link>
</li>
);
return listItems;
}
render() {
return (
<div>
<ul className="tabs" >{this.getListItem()}</ul>
<div className="tabs-header-stripe"></div>
</div>
);
}
}
export default Tabs

According to the scenario u described, you need a stateful component instead of stateless function component. Store the index of current tab in state variable and update it inside onclick method, during the rendering compare the index of state variable with the index of item, if they are same then apply the class. Try this a similar example, it should work in ur case also:
class HelloWidget extends React.Component{
constructor(props) {
super(props);
this.state = {
index: ''
};
this.onclick = this.onclick.bind(this);
}
onclick(index) {
this.setState({index});
}
getListItem(){
let numbers = this.props.menuitems;
let listItems = numbers.map((item,index) =>
<li style={{color: this.state.index==index?'red': 'black'}} className={this.state.index == index ? "tab-link current" : "tab-link"} onClick={this.onclick.bind(this, index)} key={index}>{index}-{item.number}</li>
);
return listItems;
}
render() {
return (
<div>
<ul className="tabs" >{this.getListItem()}</ul>
<div className="tabs-header-stripe"></div>
</div>
);
}
}
React.render(<HelloWidget menuitems={[{number:0, index:0}, {number:1, index:1}, {number:3, index:3}]}/>, document.getElementById('container'));
check the jsfiddle for working example: https://jsfiddle.net/27po3p4b/

the className current is only on the first tab because you are checking if index === 0 (first tab) and if true - you are adding the current class.
You need to keep a state of activeTabIndex and on your onClick function change the activeTabIndex to the right index.
and then you can check
className={index === this.state.activeTabIndex ? "tab-link current" : "tab-link"}

Related

Why all div will go open If am clicking on any one button?

I am trying to fetch the data to show the content as the frontend side. In this image
you can see I have mentioned the button with the div attributes i.e. in blue color.I tried the below code but I don't know how to create/handle the button and div.
import React from "react";
import {Col } from "react-bootstrap";
import "./style.scss"
class Example extends React.Component{
constructor(props){
super(props);
this.state = {
opencollapse: true,
}
}
toggle = () => this.setState((currentState) => ({opencollapse: !currentState.opencollapse}));
renderApps = () => {
const result = this.props.userdata.appliers.map((item, i) => {
return(
<div key={i}>
<div>
<div className="d-flex">
<Col>
<button onClick={this.toggle}>{this.state.opencollapse ? 'Click to close' : 'See Letter'}</button>
</Col>
<Col>
helloIcon
</Col>
</div>
{this.state.opencollapse && <div>{this.props.userdata.appliers[i].letter}</div>}
</div>
</div>
)
});
return result;
}
render(){
return (
<div>
{this.renderApps()}
</div>
);
}
}
export default Example;
I don't know where I did wrong.
In the image as you can see I tried to click on the number three button to open the third div but number 1 and number 2 button is also getting opened. I want to open those div which is opened by me, others let be closed. If I will click on button number one then div number one should be open and the rest should be closed vice versa.
It is because you have the same state variable for all buttons.
You can create another component.
Then each mapped component will have its own state
Child Component:
import React from "react";
import {Col } from "react-bootstrap";
import "./style.scss"
class ChildExample extends React.Component{
constructor(props){
super(props);
this.state = {
opencollapse: true,
}
}
toggle = () => this.setState((currentState) => ({opencollapse: !currentState.opencollapse}));
render(){
return (
<div>
<div>
<div className="d-flex">
<Col>
<button onClick={this.toggle}>{this.state.opencollapse ? 'Click to close' : 'See Letter'}</button>
</Col>
<Col>
helloIcon
</Col>
</div>
{this.state.opencollapse && <div>{this.props.text}</div>}
</div>
</div>
);
}
}
export default ChildExample;
renderApps function in Example component:
renderApps = () => {
const result = this.props.userdata.appliers.map((item, i) => {
return(
<ChildExample text={this.props.userdata.appliers[i].letter}
)
});
return result;
}
A good solution would be to create a seperate component for your button and the content its hiding. This way we can give each component their own state.
A simplified example can be found below:
class ToggleElement extends React.Component {
constructor(props){
super(props);
this.state = {
opencollapse: true,
};
}
toggle = () => {
this.setState((currentState) => (
{opencollapse: !currentState.opencollapse}
));
}
render() {
return (
<div>
<button onClick={this.toggle}>{this.state.opencollapse ? 'Click to close' : 'See Letter'}</button>
{this.state.opencollapse && <div>{this.props.item.letter}</div>}
</div>
)
}
}
class Example extends React.Component {
constructor(props){
super(props);
}
renderApps() {
return this.props.data.map((item, i) => (
<ToggleElement item={item} />
));
}
render() {
return (
<div>
{this.renderApps()}
</div>
);
}
}
// Render it
ReactDOM.render(
<Example data={[
{letter: 'A'},
{letter: 'B'},
{letter: 'C'},
]} />,
document.body
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
your buttons have same state, you can try to create a different state for each button, or create an array of refs with your divs with content and connect this array to your buttons via some data-index and then open the only one div that matched to button's index
You can try making the state as array of states instead of one.
For example:
The state on constructor
constructor(props){
super(props);
let initOpenCollapse = [];
const n = 5; // This is set based on how many buttons you want to create
for (let i = 0; i < n; i++) initOpenCollapse.push(true);
this.state = {
opencollapse: initOpenCollapse,
}
}
The toggle function
toggle = (nthButton) => {
let resultState = [];
for (let i = 0; i < this.state.opencollapse.length; i++) {
if (i === nthButton)
resultState.push(!this.state.opencollapse[i]);
else
resultState.push(this.state.opencollapse[i]);
}
this.setState((currentState) => ({opencollapse: resultState}));
};
And in the render():
<button onClick={() => this.toggle(i)}>{this.state.opencollapse[i] ? 'Click to close' : 'See Letter'}</button>
...
...
{this.state.opencollapse[i] && <div>{this.props.userdata.appliers[i].letter}</div>}

Toggle only the menu clicked in Reactjs

I am making a menu and submenus using recursion function and I am in the need of help to open only the respective menu and sub menu's..
For button and collapse Reactstrap has been used..
Recursive function that did menu population:
{this.state.menuItems &&
this.state.menuItems.map((item, index) => {
return (
<div key={item.id}>
<Button onClick={this.toggle.bind(this)}> {item.name} </Button>
<Collapse isOpen={this.state.isToggleOpen}>
{this.buildMenu(item.children)}
</Collapse>
</div>
);
})}
And the buildMenu function as follows,
buildMenu(items) {
return (
<ul>
{items &&
items.map(item => (
<li key={item.id}>
<div>
{this.state.isToggleOpen}
<Button onClick={this.toggle.bind(this)}> {item.name} </Button>
<Collapse isOpen={this.state.isToggleOpen}>
{item.children && item.children.length > 0
? this.buildMenu(item.children)
: null}
</Collapse>
</div>
</li>
))}
</ul>
);
}
There is no problem with the code as of now but I am in the need of help to make menu -> submenu -> submenu step by step open and closing respective levels.
Working example: https://codesandbox.io/s/reactstrap-accordion-9epsp
You can take a look at this example that when you click on any menu the whole level of menus gets opened instead of clicked one..
Requirement
If user clicked on menu One, then the submenu (children)
-> One-One
needs to get opened.
And then if user clicked on One-One,
-> One-One-One
-> One - one - two
-> One - one - three
needs to get opened.
Likewise it is nested so after click on any menu/ children their respective next level needs to get opened.
I am new in react and reactstrap way of design , So any help from expertise would be useful for me to proceed and learn how actually it needs to be done.
Instead of using one large component, consider splitting up your component into smaller once. This way you can add state to each menu item to toggle the underlying menu items.
If you want to reset al underlying menu items to their default closed position you should create a new component instance each time you open up a the underlying buttons. By having <MenuItemContainer key={timesOpened} the MenuItemContainer will be assigned a new key when you "open" the MenuItem. Assigning a new key will create a new component instance rather than updating the existing one.
For a detailed explanation I suggest reading You Probably Don't Need Derived State - Recommendation: Fully uncontrolled component with a key.
const loadMenu = () => Promise.resolve([{id:"1",name:"One",children:[{id:"1.1",name:"One - one",children:[{id:"1.1.1",name:"One - one - one"},{id:"1.1.2",name:"One - one - two"},{id:"1.1.3",name:"One - one - three"}]}]},{id:"2",name:"Two",children:[{id:"2.1",name:"Two - one"}]},{id:"3",name:"Three",children:[{id:"3.1",name:"Three - one",children:[{id:"3.1.1",name:"Three - one - one",children:[{id:"3.1.1.1",name:"Three - one - one - one",children:[{id:"3.1.1.1.1",name:"Three - one - one - one - one"}]}]}]}]},{id:"4",name:"Four"},{id:"5",name:"Five",children:[{id:"5.1",name:"Five - one"},{id:"5.2",name:"Five - two"},{id:"5.3",name:"Five - three"},{id:"5.4",name:"Five - four"}]},{id:"6",name:"Six"}]);
const {Component, Fragment} = React;
const {Button, Collapse} = Reactstrap;
class Menu extends Component {
constructor(props) {
super(props);
this.state = {menuItems: []};
}
render() {
const {menuItems} = this.state;
return <MenuItemContainer menuItems={menuItems} />;
}
componentDidMount() {
loadMenu().then(menuItems => this.setState({menuItems}));
}
}
class MenuItemContainer extends Component {
render() {
const {menuItems} = this.props;
if (!menuItems.length) return null;
return <ul>{menuItems.map(this.renderMenuItem)}</ul>;
}
renderMenuItem(menuItem) {
const {id} = menuItem;
return <li key={id}><MenuItem {...menuItem} /></li>;
}
}
MenuItemContainer.defaultProps = {menuItems: []};
class MenuItem extends Component {
constructor(props) {
super(props);
this.state = {isOpen: false, timesOpened: 0};
this.open = this.open.bind(this);
this.close = this.close.bind(this);
}
render() {
const {name, children} = this.props;
const {isOpen, timesOpened} = this.state;
return (
<Fragment>
<Button onClick={isOpen ? this.close : this.open}>{name}</Button>
<Collapse isOpen={isOpen}>
<MenuItemContainer key={timesOpened} menuItems={children} />
</Collapse>
</Fragment>
);
}
open() {
this.setState(({timesOpened}) => ({
isOpen: true,
timesOpened: timesOpened + 1,
}));
}
close() {
this.setState({isOpen: false});
}
}
ReactDOM.render(<Menu />, document.getElementById("root"));
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>
<div id="root"></div>
You will want to create an inner component to manage the state at each level.
For example, consider the following functional component (I'll leave it to you to convert to class component):
const MenuButton = ({ name, children }) => {
const [open, setOpen] = useState(false);
const toggle = useCallback(() => setOpen(o => !o), [setOpen]);
return (
<>
<Button onClick={toggle}>{name}</Button>
<Collapse open={open}>{children}</Collapse>
</>
);
};
This component will manage whether to display its children or not. Use it in place of all of your <div><Button/><Collapse/></div> sections, and it will manage the open state for each level.
Keep shared state up at the top, but if you don't need to know whether something is expanded for other logic, keep it localized.
Also, if you do need that info in your parent component, use the predefined object you already have and add an 'open' field to it which defaults to false. Upon clicking, setState on that object to correctly mark the appropriate object to have the parameter of true on open.
Localized state is much cleaner though.
Expanded Example
import React, { Component, useState, useCallback, Fragment } from "react";
import { Collapse, Button } from "reactstrap";
import { loadMenu } from "./service";
const MenuButton = ({ name, children }) => {
const [open, setOpen] = React.useState(false);
const toggle = useCallback(() => setOpen(o => !o), [setOpen]);
return (
<Fragment>
<Button onClick={toggle}>{name}</Button>
<Collapse open={open}>{children}</Collapse>
</Fragment>
);
};
class Hello extends Component {
constructor(props) {
super(props);
this.state = {
currentSelection: "",
menuItems: [],
};
}
componentDidMount() {
loadMenu().then(items => this.setState({ menuItems: items }));
}
buildMenu(items) {
return (
<ul>
{items &&
items.map(item => (
<li key={item.id}>
<MenuButton name={item.name}>
{item.children && item.children.length > 0
? this.buildMenu(item.children)
: null}
</MenuButton>
</li>
))}
</ul>
);
}
render() {
return (
<div>
<h2>Click any of the below option</h2>
{this.state.menuItems &&
this.state.menuItems.map((item, index) => {
return (
<MenuButton name={item.name}>
{this.buildMenu(item.children)}
</MenuButton>
);
})}
</div>
);
}
}
export default Hello;

set a condition inside render function and change state

I want to set a condition inside the render function.
The code is as below:
class ListItems extends React.Component{
constructor(){
super();
this.state = {
active:false,
}
this.toggleActive = this.toggleActive.bind(this);
}
toggleActive(){
this.setState({
active: !this.state.active
})
}
render(){
var demo = document.querySelector("#" + this.props.data);
if(document.body.contains(demo)){
this.toggleActive()
return(
<li className={this.state.active ? "active" : ""} onClick={this.toggleActive}>{this.props.data}</li>
)
}
else{
return(
<li className={this.state.active ? "active" : ""} onClick={this.toggleActive}>{this.props.data}</li>
)
}
}
}
it compiles successfully but returns this error while running:
Maximum update depth exceeded. This can happen when a component
repeatedly calls setState inside componentWillUpdate or
componentDidUpdate. React limits the number of nested updates to
prevent infinite loops.
use this:
class ListItems extends React.Component{
constructor(){
super();
this.state = {
active:false,
}
this.toggleActive = this.toggleActive.bind(this);
}
toggleActive(){
this.setState({
active: !this.state.active
})
}
componentDidMount(){
var demo = document.querySelector("#" + this.props.data);
if(document.body.contains(demo)){
this.toggleActive()
return(
<li className={this.state.active ? "active" : ""} onClick={this.toggleActive}>{this.props.data}</li>
)
}
}
render(){
return(
<li className={this.state.active ? "active" : ""} onClick={this.toggleActive}>{this.props.data}</li>
)
}
}
let me know if that works?
In your render you have this.toggleActive() which changes the state, causing a rerender, and which then again changes state, so this causes an intinite loop.
Just remove this line and try.
If you want to change state you can use componentDidMount lifecycle:
componentDidMount() {
this.toggleActive();
}
Change the code to following, you are basically calling the functions while rendering.
onClick={ e => {// your code} }

Create and return a new component on-click in parent component

I have two components Class and Students. The Class component renders and returns a list of Classes in <li>. I want to add click events to display the Students for each Class in Class component.
I have the following in the render method of the Class component:
render(){
const renderClasses = () =>
this.props.classes.map(class => {
return (
<li>
{class.name}
//class object also has a property of 'students'
</li>
)
})
return(
<div>
{ renderClasses() }
</div>
)
}
I want to be able to click on the anchor tags and display the corresponding students for that class. Of course the Student component should receive a prop as follows:
<Students students={this.class.students} />
Thanks in advance!
You can keep a component state to save the class index that should show its students, and then add an onClick handler on the anchor to change that index.
Try the code below:
export default class Test extends Component {
constructor(props)
{
super(props);
this.state = {activeClassIndex : -1}
this.setActiveClassIndex = this.setActiveClassIndex.bind(this);
}
setActiveClassIndex(index){
this.setState({
activeClassIndex : index
})
}
render(){
const renderClasses = () =>
this.props.classes.map( ( currentClass , index ) => {
return (
<li>
<a href="#" onClick={ () => { this.setActiveClassIndex(index) } }>{currentClass.name}</a>
{this.state.activeClassIndex == index ? <Students students={currentClass.students} /> : "" }
</li>
)
})
return(
<div>
{ renderClasses() }
</div>
)
}
}

Applying react class to a single element onMouseEnter

I failed to apply a class to a Dom node, below code will apply class to every DOM node.
import { Component } from 'react';
export default class App extends Component {
constructor(props){
super(props)
this.state = {
active: false
}
}
onMouseEnter(){
this.setState({active:true})
}
render(){
const items = [1,2,3,4,5];
return (
<div>
{items.map((obj,i) => <div key={i} className={this.state.active ? 'active' : ''} onMouseEnter={this.onMouseEnter.bind(this)}>{obj}</div>)}
</div>
);
}
}
What has gone wrong here? Also, how to do onMouseLeave? Just set this.setState({active:false}) false?
You are close... What you want is something like assigning an "active index". Your onMouseEnter() function could be changed to take the index of the active item like this
onMouseEnter(index){
this.setState({active: index})
}
And your render function would look like this instead:
render(){
const items = [1,2,3,4,5];
return (
<div>
{items.map((obj,i) =>
<div key={i} className={this.state.active === i ? 'active' : ''} onMouseEnter={this.onMouseEnter.bind(this, i)}>{obj}</div>)}
</div>
);
}
The thing you did wrong in the example you posted is not differentiating between which item in the list is in fact active instead you applied the active class to every item.
Your comments on my answer to this question make no sense:
(as you can see my mouse is no longer hovering over the active item but it is still yellow)

Categories

Resources