How to conditionally change visibility with React? - javascript

I am trying to change a div's visibility from hidden to visible using button click. But even when I am clicking the button, the visibility is not changing. I have logged the console after the clickHandler, and it still says false even after I set it to true inside the function. So far, I have this,
let clicked = false;
//change image on fortune cookie click
const clickHandler = (e) => {
e.target.setAttribute(
"src",
"https://i.ibb.co/cksx7kr/kisspng-fortune-cookie-drawing-clip-art-fortune-cookie-5b237469209879-9079770415290502171335.png"
);
clicked = true;
};
console.log(clicked);
return (
<div className="App">
<button onClick={clickHandler}>
<img
className="cookie"
src="https://i.ibb.co/9YQV2qq/kisspng-fortune-cookie-biscuits-bakery-drawing-clip-art-fortune-cookies-5b0ec5e3013c23-3980054715276.png"
/>
</button>
<div
className="fortuneMessage"
style={{ visibility: clicked ? "visible" : "hidden" }}
>
{fortune}
</div>
</div>
);

When you are using React, you need a state in order to have a DOM update. That would fork for you (don't forget to import useState at the top of your file) :
const [clicked, setClicked] = useState(false);
const [src, setSrc] = useState("https://i.ibb.co/9YQV2qq/kisspng-fortune-cookie-biscuits-bakery-drawing-clip-art-fortune-cookies-5b0ec5e3013c23-3980054715276.png");
//change image on fortune cookie click
const clickHandler = (e) => {
setSrc("https://i.ibb.co/cksx7kr/kisspng-fortune-cookie-drawing-clip-art-fortune-cookie-5b237469209879-9079770415290502171335.png");
setClicked(true);
};
console.log(clicked);
return (
<div className="App">
<button onClick={clickHandler}>
<img
className="cookie"
src={src}
/>
</button>
<div
className="fortuneMessage"
style={{ visibility: clicked ? "visible" : "hidden" }}
>
{fortune}
</div>
</div>
);

You'll need to use state instead of the plain clicked variable.
const [clicked, setClicked] = useState(false);
Call the setState function inside the clickHandler function instead of setting clicked to true.
setClicked(true);

in React, the variable is not two way binding.
The clicked variable in your function will only get to be true in the function itself but not outside the function
So in this case you need to use useState so that the clicked state will be re-rendered again with updated boolean
const [clicked, setClicked] = useState(false)
const clickHandler = () => {
setClicked(true)
}
so that your clicked variable will be updated, hence the css will be updated again.
for more reference with useState can check their documentation https://reactjs.org/docs/hooks-state.html

Related

Add animation to filter items on click

I created a filter gallery. I want to animate the filter items every time I click to a buttons. But my codes are not doing it properly. It animates filter items like toggle. If I click on a button first time it animates items, then If I click on another button it shows nothing. After that If I click on another button it animates again. What's wrong with my code? Experts please help me to find out the proper solution. Thanks in advance.
Here is my code:
import React, { useState } from 'react';
import suggestData from '../data/suggest-data.json';
const allCategories = ['All', ...new Set(suggestData.map(item => item.area))];
const Suggest = () => {
const [suggestItem, setSuggestItem] = useState(suggestData);
const [butto, setButto] = useState(allCategories);
const [selectedIndex, setSelectedIndex] = useState(0);
const [anim, setAnim] = useState(false);
const filter = (button) => {
if (button === 'All') {
setSuggestItem(suggestData);
return;
}
const filteredData = suggestData.filter(item => item.area === button);
setSuggestItem(filteredData);
}
const handleAnim = () => {
setAnim(anim => !anim);
}
return (
<div>
<h1>Suggest</h1>
<div className="fil">
<div className="fil-btns">
<div className="fil-btn">
<button className='btn'>Hello</button>
{
butto.map((cat, index) => {
return <button type="button" key={index} onClick={() => { filter(cat); setSelectedIndex(index); handleAnim(); }} className={"btn" + (selectedIndex === index ? " btn-active" : "")}>{cat}</button>
})
}
</div>
</div>
<div className="fil-items">
{
suggestItem.map((item, index) => {
return (
<div className={"fil-item" + (anim ? " fil-item-active" : "")} key={index}>
<h1>{item.name}</h1>
<h2>{item.category}</h2>
<h3>{item.location}</h3>
<h4>{item.type}</h4>
<h5>{item.area}</h5>
</div>
);
})
}
</div>
</div>
</div>
);
}
export default Suggest;
In your handleAnim() function, you are simply toggling the value of anim state. So initially, its value is false and when you click the button for the first time, it is set to true. On clicking the next button, the anim state becomes false because the value of !true is false and hence your animation doesn't work. On next click, becomes true again since !false is true and the toggle continues again and again.
If you want to make your animations work on every click you will need to set the anim state value to true on every button click as below since you seem to depend on the value to set animations. As an alternative, I think it will do just fine if you simply add the animation directly to the enclosing div with class .filter-item instead of relying on states to trigger the animation since after every filter you apply, the elements will be re-rendered and the animation will happen after every re-render.
const handleAnim = () => {
setAnim(true);
}

Get getAttribute from button and toggle class name to body react hooks

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

How to scroll beyond the focused element in React

I am working on a React App, and using antd as the UI-library. What I intend to do is whenever a button named Open Modal is clicked, a Modal will open up, and when user clicks on the Ok button in the modal, an input field will be displayed and page should automatically be scrolled to the top. However, since the input field contains focus, the scroll happens only until the input field becomes visible. Is there any way to make the page scroll to top, ignoring focused elements using JS only. This is the minimal code to reproduce the example:
const App = () => {
const [isModalVisible, setIsModalVisible] = useState(false);
const [isInputVisible, setIsInputVisible] = useState(false);
const showModal = () => {
setIsModalVisible(true);
};
const handleOk = () => {
setIsInputVisible(true);
window.scroll(0, 0);
};
const handleCancel = () => {
setIsInputVisible(false);
setIsModalVisible(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
visible={isModalVisible}
onOk={handleOk}
onCancel={handleCancel}
>
/*Some contents...*/
{isInputVisible && <input autoFocus />}
/*Some contents...*/
</Modal>
</>
);
};
This is the sandbox link to reproduce the use-case.
Code Sandbox
You can always use scrollIntoView method.
scrollIntoView
I tried with ref and useRef but it didnt work, so the other solution was to find the modal through it's class name and use the above method.
Check this
sandbox
I am not sure if it will work but I think you can work your way through with the useRef hook where you can focus after the click event. Refer this

Adapting the page's backgroundColor (that can be changed anytime) to a button's backgroundColor in React

I have an ArbitraryBtn that changes the page's color into random colors on every click:
import React from 'react';
export const changeToArbitraryColor = () =>
(document.body.style.backgroundColor =
'#' + Math.floor(Math.random() * 16777215).toString(16));
const ArbitraryBtn = () => (
<button
className='arbitrary-btn'
onMouseDown={changeToArbitraryColor}></button>
);
export default ArbitraryBtn;
And I also have this AppendBtn. I want the AppendBtn to have the same background-color as the document's page. So I did this on the style attribute:
import React from 'react';
const AppendBtn = () => {
return (
<button
className='append-btn'
style={{ backgroundColor: document.body.style.backgroundColor }}>
+
</button>
);
};
export default AppendBtn;
But the problem with this is that it only takes the background-color of the page when the component is mounted, and when I click on the ArbitraryBtn and it changes the background-color of the page, the AppendBtn doesn't adapt to the new background-color of the page anymore.
I tried exporting a function (so that I could call this function inside the changeToArbitraryColor function) that uses useRef hook for the style attribute. But since React Hook "useRef" cannot be called at the top level and React Hooks must be called in a React function component or a custom React Hook function, and 'import' and 'export' may only appear at the top level, it didn't work out.
Here's a gif when I was using JavaScript (but I want to do this in React):
The button on the left side is the ArbitraryBtn and the one on the right is the AppendBtn. How can I achieve this in React? Please help.
Here is how you can achieve this:
Idea is once the body background is changed on click of the ArbitraryBtn. The same time I am passing a state to the child component isClicked. Now every time isClicked changes/ toggles my useEffect() in the child component will get invoked. At that very time, I am setting the background of the AppendBtn same as the body background using normal javascript :)
Codesandbox Demo:
https://codesandbox.io/s/button-issue-h6h2l?file=/src/App.js
FULL CODE:
const { useEffect, useRef, useState }= React;
function App() {
const [isClicked, setIsClicked] = useState(true);
const changeToArbitraryColor = () => {
document.body.style.backgroundColor =
"#" + Math.floor(Math.random() * 16777215).toString(16);
setIsClicked(!isClicked);
};
return (
<div className="App">
<ArbitraryBtn changeToArbitraryColor={changeToArbitraryColor} />
<AppendBtn isClicked={isClicked} />
</div>
);
}
const ArbitraryBtn = ({ changeToArbitraryColor }) => (
<button className="arbitrary-btn" onClick={changeToArbitraryColor}>
I am here
</button>
);
const AppendBtn = ({ isClicked }) => {
const childRef = useRef(null);
console.log("Here I am");
console.log(document.body.style.backgroundColor);
useEffect(() => {
childRef.current.style.backgroundColor =
document.body.style.backgroundColor;
});
return (
<button className="append-btn" ref={childRef}>
+ I am the Append Button
</button>
);
};
ReactDOM.render(<App/>,document.querySelector('#root'))
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>

Next.js toggle display of a div tag

Code
export default function Header(){
let showMe = false;
function toggle(){
showMe = !showMe;
}
return (
<>
<button onClick={toggle}>Toggle Subjects</button>
{/*The bottom code should toggle on and off when the button is pressed*/}
<div style={{
display: showMe?"block":"none"
}}>
This should toggle my display
</div>
</>
);
}
Expectation
The div tag should toggle in visibility (For example, if I clicked on the button once, the div tag should show up, and if I clicked on it again it would be hidden and so on).
Reality
It appears the variable showMe changes however the div tag does not follow with the updates and remains hidden.
NOTE: I am using next.js if that changes anything.
showMe needs to be a state variable so that React knows to rerender the component when showMe changes. I'd read this: https://reactjs.org/docs/hooks-state.html
The code below should work (note how showMe is replaced with a call to useState).
export default function Header(){
const [showMe, setShowMe] = useState(false);
function toggle(){
setShowMe(!showMe);
}
return (
<>
<button onClick={toggle}>Toggle Subjects</button>
{/*The bottom code should toggle on and off when the button is pressed*/}
<div style={{
display: showMe?"block":"none"
}}>
This should toggle my display
</div>
</>
);
}
The bracket notation const [showMe, setShowMe] = useState(false); is Array Destructuring: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
useState returns an array of length 2. Using array destructuring, we set the first element of the returned array to showMe and the second element of the returned array to setShowMe.

Categories