React Toggle ID - javascript

I have an issue with a toggling div. It is probably a small thing, but I can not find the reason why it works the first time I click on it (and the panel expands), but when clicking again it does not close. Maybe someone can see why the second time I click on the item, the id is not being send along with the element "e"? Thanks a lot!
class FlexPanel extends Component {
constructor(props) {
super(props);
this.state = {
toggle1: false,
toggle2: false,
toggle3: false,
};
}
render() {
const toggleOpen = (e) => {
const id = e.target.id;
const toggleId = `toggle${id}`;
let toggleItem = this.state[toggleId];
this.setState({
[toggleId]: !toggleItem,
});
};
const { toggle1, toggle2, toggle3 } = this.state;
console.log(toggle1);
return (
<div className="panels">
<div
id={1}
className={`panel panel1 ${toggle1 ? "open open-active" : "closed"} `}
onClick={(e) => {
toggleOpen(e);
}}
>
<p>Consultes</p>
<p>Teràpies</p>
<p>Recolzament</p>
</div>
<div
id={2}
className={`panel panel2 ${toggle2 ? "open open-active" : "closed"} `}
onClick={(e) => {
toggleOpen(e);
}}
>
<p>Videoconsultes</p>
<p>en grup</p>
<p>i individuals</p>
</div>
<div
id={3}
className={`panel panel3 ${toggle3 ? "open open-active" : "closed"} `}
onClick={(e) => {
toggleOpen(e);
}}
>
<p>Jordi</p>
<p>Arjó</p>
<p>Teràpies</p>
</div>
</div>
);
}
}
export default FlexPanel;

In toggleOpen() you need to change const id = e.target.id; to const id = e.currentTarget.id
Check out the modified code here
More about difference between e.target and e.currentTarget in the official documentation

You need to use updater function for setState
this.setState((prevSate) => {
let toggleItem = prevState[toggleId];
return {
[toggleId]: !toggleItem,
}
});

Related

How to Close Floating Menu when clicking anywhere else but the menu? React JS

I was struck while creating a Floating menu in react JS, the task required is to close the floating menu when a click is recorded anywhere outside the menu, the code snippet is as follows
class SectionMenu extends Component {
state = {
isOpen: false,
activeSection: '',
sectionIds: [],
coordinates: []
}
render() {
const { sections, hideMenu } = this.props;
const { sectionIds } = this.state;
return (
<React.Fragment>
<FloatingMenu
id="floating-menu"
slideSpeed={10}
direction="left"
isOpen={this.state.isOpen}
spacing={16}
style={hideMenu ? {display: "none"} : null}
>
<MainButton
iconResting={}
iconActive={}
onClick={() => this.setState({isOpen:
!this.state.isOpen})}
size={}
/>
{"Random Code to bring up each menu item"}
</FloatingMenu>
</React.Fragment>
)
}
}
I am struck on how to create a useRef/useEffect hook function, is there any method to to it done without useRef as well?
if not how do I do it with useRef/useEffect and/or any other hook?
Try to refactor your code using functional component:
const SectionMenu = ({ sections, hideMenu }) => {
const [sectionsIds, setSectionsIds] = useState([]);
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
// whatever effect
}, [])
return (
<React.Fragment>
<FloatingMenu
id="floating-menu"
slideSpeed={10}
direction="left"
isOpen={isOpen}
spacing={16}
style={hideMenu ? {display: "none"} : null}
>
<MainButton
onClick={() => setIsOpen((prev) => !prev)}
/>
{"Random Code to bring up each menu item"}
</FloatingMenu>
</React.Fragment>
)
}
Yes, I've ran up into the same issue and this worked for me . You can change the onClick() as follows
let isClicked=true;
const {/*function name here*/} =()=>{
if(isClicked){
document.querySelector("{/*classname here*/}").style = "display:block "
isClicked=false;
}
else{
document.querySelector("{/*classname here*/}").style = "display:none"
isClicked=true;
}
}
This will toggle between true and false.

I need to open and close accordion based on arrow click

I am using Material UI accordion my issue is if I click on the arrow accordion will get open but again I click on the arrow it will not get closed I need to set it when the user clicks on the arrow according will close and open based on the arrow click check code sandbox link for better understanding.
export default function ControlledAccordions() {
const [expanded, setExpanded] = React.useState(false);
// const handleChange = (panel) => (event, isExpanded) => {
// setExpanded(isExpanded ? panel : false);
// };
const handleChange = (pannel) => {
setExpanded(pannel);
};
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
return (
<Accordion expanded={expanded === `panel${i}`}>
<AccordionSummary
expandIcon={
<ExpandMoreIcon
onClick={() => {
handleChange(`panel${i}`);
}}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
fdsfdsf
</AccordionSummary>
<AccordionDetails>dfdf</AccordionDetails>
</Accordion>
);
})}
</div>
);
}
Code SandBox Link
you need to reset panel in that case. You can do that in change handler.
const handleChange = (pannel) => {
setExpanded(expended === pannel ? '' : pannel);
};
when you click the already expanded panel, it just sets it to be expanded again.
you need to check whether the clicked panel is already expanded and if so collapse it instead of expanding it:
const handleChange = (pannel) => {
if (expanded === pannel) setExpanded(false);
else setExpanded(pannel);
};
Create another component called MyAccordian and keep toggling accordion logic in that component. That way you don't need to handle toggling for each and every component separately.
export default function ControlledAccordions() {
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
return <MyAccordian value={value} />;
})}
</div>
);
}
const MyAccordian = ({ value }) => {
const [expanded, setExpanded] = React.useState(false);
return (
<Accordion expanded={expanded}>
<AccordionSummary
expandIcon={
<ExpandMoreIcon
onClick={() => {
setExpanded((prev) => !prev);
}}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
{value}
</AccordionSummary>
<AccordionDetails>{value}</AccordionDetails>
</Accordion>
);
};
Working Demo
export default function ControlledAccordions() {
// initial state, everything is closed,
const [expandedIndex, setExpandedIndex] = React.useState(-1);
// this should be handleClic
const handleChange = (index) => {
// in useState, current expandedIndex is passed as the argument
// whatever we return will be set as the expandedIndex
setExpandedIndex((currentIndex) => {
// if any box is open, currentIndex will be that index
// when I click on the open box, it will set the expandedIndex=-1
if (currentIndex === index) {
return -1;
} else {
// If I reached here, that means I am on a closed box
// when I click I swithc the expandedIndex to current box's index
return index;
}
});
};
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
// when handleChange runs on AccordionSummary expandedIndex===i
// that means when i click on the current box, it will be open
const isExpanded = expandedIndex === i;
return (
<Accordion expanded={isExpanded}>
<AccordionSummary
onClick={() => handleChange(i)}
expandIcon={
// I dont know #mui/material too much.
// main question is "I need to open and close accordion based on arrow click"
<ExpandMoreIcon
onClick={() => handleChange(i)}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
{value}
</AccordionSummary>
<AccordionDetails
style={{ backgroundColor: "green" }}
>{`box index ${i} is open`}</AccordionDetails>
</Accordion>
);
})}
</div>
);
}
proof of work:
const handleChange = (pannel) => {
setExpanded(!pannel);
};

Show and hide looped elements in ReactJs

I loop through an array of elements:
this.props.connections.map((connection) => (
For each element in this array a card is created. In this card, I implemented a toogle button:
<div id="bookmarkIcon">
{this.state.available ? (
<Tab onClick={this.handleChange} icon={<StarBorderIcon/>}
aria-label="StarBorder"/>) : <Tab onClick={this.handleChange} icon={<StarIcon/>}
aria-label="StarIcon"/>}
</div>
The handle change method changes the value of available to false. The problem is that then I change the state and therefore, ever icon toggles, but I just want to toggle the icon I clicked on. How can I achieve this?
You can create an object which keeps the state as keys.
Here is a working example:
hidden will look something like this {0: true, 1: true, 2: false}
so we can update the corresponding items by their index.
https://codesandbox.io/s/intelligent-black-83cqg?file=/src/App.js:0-577
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [hidden, setHidden] = useState({});
const list = ["aa", "bb", "cc", "dd"];
const toggleHide = index => {
setHidden({ ...hidden, [index]: !hidden[index] });
};
return (
<div className="App">
{list.map((item, index) => (
<div>
{!!hidden[index] && <span>[HIDDEN]</span>}
{!hidden[index] && <span>[VISIBLE]</span>}
{item} <span onClick={e => toggleHide(index)}>x</span>
</div>
))}
</div>
);
}
Class-Based Component
class PrivacyPolicyDetails extends Component {
constructor(props) {
super(props);
this.state ={
resultData:[],
error:false ,
active: false,
activeIdList:[]
}
this.toggleClass.bind(this);
}
componentDidMount() {
setting.getQuestionAnswerByType('privacy-policy')
.then(res =>{
if(res.data.questionAnswerList.length > 0){
this.setState({
resultData: res.data.questionAnswerList,
})
}else{
this.setState({
error:true
});
}
}
);
}
toggleClass(id) {
const currentState = this.state.active;
this.setState({ active: !currentState});
if(this.state.activeIdList.find(element => element == id)){
this.state.activeIdList = this.state.activeIdList.filter(item => item !== id);
}else{
this.state.activeIdList.push(id);
}
}
render() {
const { product, currency } = this.props;
const {resultData,error,activeIdList} = this.state;
return (
<div>
<h1>Privacy Policy</h1>
{resultData && resultData.length > 0 ? resultData.map(each_policy =>
<div className="item">
<div className="question"
onClick={() => this.toggleClass(each_policy.question_answer_repository_id)}
>
{each_policy.question}
</div>
<p className={(activeIdList.find(element => element == each_policy.question_answer_repository_id))? "active-div" :"hide"}>
<div className="answer">{each_policy.answer}</div>
</p>
</div>
):''}
</div>
);
}
}
const mapStateToProps = (state) => {
return state.setting;
};
export default connect(mapStateToProps)(PrivacyPolicyDetails);
css
.hide{
display: none;
overflow:hidden;
}
.active-div{
display: block;
overflow:hidden;
}
Make the card into its own component and implement the state of the toggle inside of that component. In your parent component just map each card into one of these components. Each card will have its own toggle which uses the state of the card to determine how it should display.

How to toggle 'className' dynamically in react.js?

I want to set the active class dynamically in react.js but it's not working!
I'm using the setState() method to change the selected item.
this line of code not work .
className={selectedCategoryId === item.id ? 'active' : ''}
I think the setState() function does not work correctly...
const {open, selectedProduct, productCategory, filteredProducts, selectedCategoryId} = this.state;
const categoryItems = productCategory.map((item) =>
<a key={item.id}
onClick={() => this.handleFilter(item.id)}
className={selectedCategoryId === item.id ? 'active' : ''}
// className={()=>this.isActive(item.id)}
className="pointer"
>{item.value}</a>
);
this does not change the class:
handleFilter = (id) => {
const filteredItem = this.state.productList.filter(x => x.categoryId == id);
this.setState({filteredProducts: filteredItem, selectedCategoryId: id});
}
but this change the className correctly when select I all tags:
handleRemoveFilter = () => {
this.setState({filteredProducts: this.state.productList, selectedCategoryId: 0});
}
//-------------------------------
<div className="tag-list">
<a onClick={this.handleRemoveFilter}
className="pointer"
className={ this.state.selectedCategoryId === 0 ? 'active' : ''}
>All tags</a>
{categoryItems}
</div>
If setState() works well, try this :
<a onClick={this.handleRemoveFilter}
className={ this.state.selectedCategoryId === 0 ? 'pointer active' : 'pointer'}
>All tags</a>
One of the most common ways is to use classnames which you can conditionally joining classNames together
var classNames = require('classnames');
class Button extends React.Component {
// ...
render () {
var btnClass = classNames({
btn: true,
'btn-pressed': this.state.isPressed,
'btn-over': !this.state.isPressed && this.state.isHovered
});
return <button className={btnClass}>{this.props.label}</button>;
}
}
store classname in state along with selected item. You can just update the classname in state whenever required.
for eg,
<a key={item.id}
onClick={() => this.handleFilter(item.id)}
className={this.state.activeClassName}
where active classname can be updated inside handlefilter
We can toggle class name dynamically like below,
const [islight, setIslight] = useState(false)
const toggle = () => {
setIslight(!islight)
}
return (
<div className={`block ${islight ? "blocklight" : "blockdark"}`}>
<h2>Hello World</h2>
</div>
)

How to switch element back to its original color after focus is switched to next element

I render a list of items that are contentEditable. When I switch focus to the second element, the first element is still white. I thought the color switch statement (using this.state.active check) would work but clearly, I'm lacking in my thinking here. How would you go about it? Must I implement the logic in Comments component/container instead?
In parent container comments.tsx, I render the comments with;
<div className="comments">
<div>
{comments.map((value, key) => {
return (
<Comment key={key} profile={this.state.profile} data={value}/>
);
})
}
</div>
</div>
</div>
and in comment.tsx, I have;
interface IProps {
key: number;
profile: IProfile;
data: object;
}
export class Comment extends React.Component<IProps, any> {
constructor(props: IProps) {
super(props);
this.state = {
editableColor: 'green',
active:false
}
}
editReview = (e, data) => {
let { _id, user, comm } = data;
this.setState({active: true}, function () {
this.state.active ? this.setState({editableColor:'#ffffff'}) : this.setState({editableColor:'green'});
});
}
render() {
let { key, profile, data } = this.props;
return(
<div className="col-8 comment">
<p id="comment" contentEditable style={{backgroundColor: this.state.editableColor}}
onFocus={(e) => this.editReview(e, data)}
>
{data['comm']}
</p>
<button onClick={(e) => this.update(e, data)}>Update</button>
</div>
);
}
}
It seems to me that you never come back from the active state, you should implement an onBlur event handler to revert state.active to false:
...
<p
id="comment"
contentEditable style={{backgroundColor: this.state.editableColor}}
onFocus={(e) => this.editReview(e, data)}
onBlur={ () => this.setState({ active: false }) }
>
{data['comm']}
</p>
...
Was actually easy. As always I should have passed editableColor as a prop to Component in comments.tsx
<Comment key={key} profile={this.state.profile} data={value} editableColor={'green'}/>
Pull it out as a prop in comment.tsx
let { key, profile, data, editableColor } = this.props;
Switch out the color depending on focus/blur
editReview = (e, data) => {
this.setState({active: true});
}
<p
id="comment"
contentEditable
style={{backgroundColor: this.state.active ? editableColor='#ffffff' : editableColor}}
onFocus={(e) => this.editReview(e, data)}
onBlur={ () => this.setState({ active: false }) }
>

Categories