i am using a functional component, and have created a sidebar menu structure , with parent child way (kind of dropdown). So when I click on parent an active class should be toggle and on its next element toggle style> display> none/block.
<Fragment key={index}>
<button
className='dropdown-btn bg-transparent haschild'
style={{ border: 'none' }}
>
{data.name} <i className='fas fa-chevron-right'></i>
</button>
<div
className='dropdown-container'>
<Content
config={menuItem.children}
children={true}
/>
</div>
</Fragment>
and on use effect
useEffect(() => {
var dropdown = document.getElementsByClassName("haschild");
var i;
for (i = 0; i < dropdown.length; i++) {
dropdown[i].addEventListener("click", function () {
this.classList.toggle("active");
var dropdownContent = this.nextElementSibling;
if (dropdownContent.style.display == "none") {
dropdownContent.style.display = "block";
} else {
dropdownContent.style.display = "none";
}
});
}
},[]);
wherever i have class has child
on that element i have to toggle a class active
and on its next siblings , want to add style attr with display
When using a Framework like react that doesn't directly manipulate the DOM it's not recommended to change style like that.
What you should do instead is to either work with classes and toggle them in react, not using document.getElementsByClassName or similar functions.
This could look something like this, depending on your need:
const ToggleButton = () => {
const [active, setActive] = useState(false);
return (
<button onClick={() => setActive(prev => !prev)} className={active ? 'active' : 'inactive'}>
Toggle active
</button>
);
};
This component would render a button that can be clicked to toggle between the classes active and inactive. The rest can and perhaps should be done with purely CSS.
Modern CSS combinators like ~ can be used to affect following elements. You can read more about CSS combinators here
Related
I have a multi page form that renders a list of buttons for each possible answer.
I am currently using getElementByID to change the button style when a button is clicked. I think this is not considered good practice in react.
can I use the useRef hook to target the DOM element and change its style instead of using getElementByID if the buttons are dynamically displayed with map?
{answers.map((answer, count = 0) => {
return (
<Button
key={count}
id=`button${count}`
onClick={(e) => {
document.getElementById(`${e.currentTarget.id}`).style.color = "white";
}}
>
<ButtonTitle>{answer.name}</ButtonTitle>
</Button>
);
})}
(animations on safari are breaking the active style, so I can't use the active pseudo element)
You can do this by adding a new state and toggle active class by that state.
Code something like this.
const [activeindex, setActiveIndex] = useState("");
return(
<>
{answers.map((answer, count = 0) => {
return (
<Button
key={count}
className={count==activeindex?"acvie":""}
onClick={(e) => {
setActiveIndex(count)
}}
>
<ButtonTitle>{answer.name}</ButtonTitle>
</Button>
);
})}
}
</>
And use .active class in css file.
Hi I'm new to ReactJS so I'm importing some work of mine to my project and turning it into components. I have a span acting as a button with keyframe animations, but when i load the page it gets animated.
One solution i have is to make a .class.animation and give the .animation class on the first click.
The thing is that I know how to code it in jquery but not on react.
The idea is to convert this into react:
$('.navTrigger').onClick(function () {
$(this).addClass('animations');
});
Right now I have this:
const [isActive, setActive] = useState("false");
const handleToggle = () => {
setActive(!isActive);
};
return (
<span onClick={handleToggle} className={`navTrigger ${isActive ? "" : "animations"} ${isActive ? "" : "active"}`}>
<i></i>
<i></i>
<i></i>
</span>
);
I have a function where i toggle the active class to run backward and forward animations.
What the code below does is on web load, the span starts with navTrigger class. The first time you click it its's given animations and active class. And then if you interact with it again it takes out active class and leaves animations there
function MenuButton() {
const [isActive, setActive] = useState(false);
const [animating, setAnimating] = useState(false);
const handleToggle = () => {
setActive(!isActive);
setAnimating(true);
};
return (
<span onClick={handleToggle} className={`navTrigger ${animating ? "animations" : ""} ${isActive ? "active" : ""}`}>
<i></i>
<i></i>
<i></i>
</span>
);
}
i want to improve my code, with several buttons that has custom class names (attr), when clicked should add to body tag (toggle), now is adding the first button only because for ("button")[0] but should work for each button
import React, { useState, useEffect } from "react"
function Test() {
const [isClass, setIsClass] = useState(false)
useEffect(() => {
const x = document.getElementsByTagName("button")[0].getAttribute("custom-class")
document.body.classList.toggle(x, isClass)
}, [isClass])
return (
<>
<button custom-class='test1' onClick={() => setIsClass(!isClass)}>
Setting test1 className
</button>
<button custom-class='test2' onClick={() => setIsClass(!isClass)}>
Setting test2 className
</button>
</>
)
}
export default Test
Thanks
Please use this code.
let oldStyle = "";
const handleClick = (index) => {
const x = [...document.getElementsByTagName("button")].map(value => value.getAttribute("custom-class"));
document.body.classList.contains(x[index]) ? document.body.classList.remove(x[index]) : document.body.classList.add(x[index]);
if(document.body.classList.length > 1) document.body.classList.replace(oldStyle, x[index]);
oldStyle = x[index];
}
return (
<>
<button custom-class='test1' onClick={() => handleClick(0)}>
Setting test1 className
</button>
<button custom-class='test2' onClick={() => handleClick(1)}>
Setting test2 className
</button>
</>
)
It is better not to use DOM querying and manipulation directly with elements that are created and controlled by react. In your particular example it is ok to use document.body, but not ok to search for buttons, especially when you try to find them by tag name. To actually toggle a class in classList you don't need second parameter in most cases, so additional state is also not needed.
React way to get reference to element renderend by React would be to use Ref. However, in your particular case side effect can be launched inside event handler, so you don't need useEffect or useRef.
Your onClick handler can accept event object that is Synthetic Event. It holds property target that holds reference to your button.
So, the easiest way would be simply to write like this:
function Test() {
function clickHandler(event) {
let classToToggle = event.target.getAttribute("custom-class");
document.body.classList.toggle(classToToggle);
}
return (
<>
<button key="test1" custom-class="test1" onClick={clickHandler}>
Setting test1 className
</button>
<button key="test2" custom-class="test2" onClick={clickHandler}>
Setting test2 className
</button>
</>
);
}
export default Test;
If you need to have only single className from the list, you can decide which class to enable or disable with a bit of a state. Since anything can add classes on body it might be useful to operate only on some set of classes and not remove everything.
Also, not mentioned before, but consider using data attribute as its purpose is to keep some additional data.
function Test() {
// this can come from props or be hardcoded depending on your requirements
// If you intend to change it in runtime, consider adding side effect to cleanup previous classes on body
let [classesList] = React.useState(["test1", "test2"]);
let [activeClass, setActiveClass] = React.useState("");
// You can switch actual classes in effect, if you want to
function clickHandler(event) {
let classToToggle = event.target.dataset.customClass;
// we remove all classes from body that are in our list
document.body.classList.remove(...classesList);
if (activeClass === classToToggle) {
setActiveClass("");
} else {
// if class not active - set new one
document.body.classList.add(classToToggle);
setActiveClass(classToToggle);
}
}
return (
<>
{classesList.map((cn) => (
<button key="cn" data-custom-class={cn} onClick={clickHandler}>
Setting {cn} className
</button>
))}
</>
);
}
I have a column list of elements, and each of them has hidden div, I want to show hidden element on click, but when I'm clicking another div, I want to hide current and show the one I clicked last, how can I do it correctly?
You could have two specific classes, one that hides element and one that shows element. when clicking on the element you can use jQuery or JavaScript to toggle the class that shows element for the hidden div of that specific element and hide everything for any other div.
The component you're rendering could take in an active prop and only render the second div if this prop is true.
With this, you could then keep track of which element is selected in the parent.
I threw together a working example with very simple content
import React, { useState } from 'react';
const ListItem = ({id, isActive, handleClick}) => {
return (
<div id={id} onClick={handleClick}>
Here is the element
{!!isActive && <div>I am the selected element</div>}
</div>
);
};
export const List = () => {
const [selectedItem, setSelectedItem] = useState();
const items = ['one', 'two', 'three'];
const handleClick = (event) => {
setSelectedItem(event.target.id);
}
return (
<div>
{items.map(item => (
<ListItem id={item} isActive={item===selectedItem} handleClick={handleClick}/>
))}
</div>
)
}
I have a react component like this -
const MyComponent = () => (
<ContainerSection>
<DeleteButtonContainer>
<Button
theme="plain"
autoWidth
onClick={() => {
onDeleteClick();
}}
>
Delete
</Button>
</DeleteButtonContainer>
</ContainerSection>
);
I want to show the DeleteButtonContainer only when the user hovers over ContainerSection. Both of them are styled-components. I couldn't find any way to do it using just css (using hover state of parent inside child), so I used something like this using state -
const MyComponent = ()=>{
const [isHoveredState, setHoveredState] = useState<boolean>(false);
return (<ContainerSection onMouseEnter={() => setHoveredState(true)} onMouseLeave={() => setHoveredState(false)}>
<DeleteButtonContainer style={{ display: isHoveredState ? 'block' : 'none' }}>
<Button
theme="plain"
autoWidth
disabled={!isHoveredState}
onClick={() => {
onDeleteClick();
}}
>
Delete
</Button>
</DeleteButtonContainer>
</ContainerSection>)
};
Now I want to always show DeleteButtonContainer when it's on mobile device since it doesn't have hover. I know I can always right more JS to achieve this, but I want to do it using CSS and if possible I want to remove state completely.
So is there a way to achieve this using just styled component and not writing custom JS?
You can reference one component in another, and use media queries to enable the rule for non mobile resolutions.
Hover the the golden bar to see the button, and shrink the width to disable the hover rule.
const DeleteButtonContainer = styled.div``;
const ContainerSection = styled.div`
height: 2em;
background: gold;
#media (min-width: 640px) { // when resolution is above 640px
&:not(:hover) ${DeleteButtonContainer} { // if DeleteButtonContainer is not under an hovered ContainerSection
display: none;
}
}
`;
const Button = styled.button``;
const MyComponent = () => (
<ContainerSection>
<DeleteButtonContainer>
<Button>
Delete
</Button>
</DeleteButtonContainer>
</ContainerSection>
);
ReactDOM.render(
<MyComponent />,
root
);
<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/styled-components/4.4.0/styled-components.js"></script>
<div id="root"></div>