I have some code that looks like this
const Movies = () => {
const [show, setShow] = useState(false);
const [show1, setShow1] = useState(false);
const onClick = () => setShow(!show);
const onClick1 = () => setShow1(!show1);
return (
<div className="movie">
<Header />
<h2>Discover a new movie!</h2>
<div className="showBtns">
<button onClick={onClick} className="right">Search <FaSearch /></button>
<button onClick={onClick1}>Discover <FaSearch /></button>
</div>
{show1 ? <DropDown /> : null }
{show ? <MovieSearch /> : null }
<Nav />
</div>
);
};
as of right now if I click on the button for either one it will show the corresponding component but if both are clicked they both show up.
I'd like to write an if else statement to check if one is showing then the other should not be shown.
I've tried a few different things and can't seem to get it to work.
any feedback on a better way to do this or how to get it to work would be appreciated.
if(show === true){
setShow1(false)
} else if(show1 === true) {
setShow(false)
}
this gives an error of Too many re-renders. React limits the number of renders to prevent an infinite loop.
You can handle the hiding/showing logic for these button in the click events because that is where the state changes.
Example:
https://codesandbox.io/s/wild-water-f5nzor?file=/src/App.js
You can modify your onClick functions like this:
const onClick = () => setShow((prevState) => !show1 && !prevState); const onClick1 = () => setShow1((prevState) => !show && !prevState);
Related
I currently have my toggle action in place but the only help I need is that, I would like to close the div as well, like an toggle action. The one that I've currently done is that once I click on another div element the previous one that has been clicked closes, but I'd rather prefer that I have an toggle action on closing and opening on the div element being clicked, without needing to click on another just to close the previous div, I've only grabbed the parts that are needed in the code, just to prevent on copying and pasting the whole file, just to save time on reading.
Code Snippet
const [posts, setPosts] = useState([]);
const [commentState, commentChange] = useState({
activeObject: null
});
const toggleComment = (index) => {
commentChange({...commentState, activeObject: posts[index]})
}
const toggleActiveStyles = (index) => {
if(posts[index] === commentState.activeObject) {
return "dashboard__commentContent toggle";
} else {
return "dashboard__commentContent";
}
}
return error ? (
<span>{error}</span>
) : (
{posts.map((post, i) => (
<button onClick={() => toggleComment(i)} >toggle</button>
<div className={toggleActiveStyles(i)}>
<h1>{post.title}</h1>
</div>
)}
Here is a working codesandbox that you can manipulate to fit to your needs.
Explanation
You would want to keep track of toggled divs and make sure to adjust your class based on that. You can filter out or add to the toggled divs state variable, and do whatever you want while rendering.
Code
import { useState } from "react";
import "./styles.css";
const DATA = ["1", "2", "3", "4"];
export default function App() {
const [closedDivs, setClosedDivs] = useState([]);
const toggleDiv = (i) => {
setClosedDivs((divs) =>
divs.includes(i) ? divs.filter((d) => d !== i) : [...divs, i]
);
};
return (
<div className="App">
{DATA.map((d, i) => (
<div
className={`${closedDivs.includes(i) ? "close" : ""} box`}
onClick={() => toggleDiv(i)}
>
<p> {d} </p>
</div>
))}
</div>
);
}
I'm building a simple dropdown with react and functional components. On strange behavior, I've run into is the way we have to think about conjures and state. This is a simplified version of my component:
export default function App() {
const [show, setShow] = useState(false);
const selectElement = useRef(null);
const handleToggle = (e) => {
if (selectElement) {
if (!selectElement.current.contains(e.target)) {
setShow(!show);
}
}
};
useEffect(() => {
document.addEventListener("click", handleToggle, false);
return () => document.removeEventListener("click", handleToggle, false);
}, []);
return (
<div className="App">
<div ref={selectElement} className="comp">
<h1 onClick={() => setShow(!show)}>Select</h1>
{show && (
<div>
<div>Inner 1</div>
<div>Inner 2</div>
</div>
)}
</div>
</div>
);
}
This component behaves wrong and it's not possible to toggle the dropdown correctly. The effect handler is registered on the first render and encloses the state of the first render (if I'm not wrong here). The registered function will not receive state updates. This is causing the error.
I'm not really sure what's the best way to fix this. Currently, I decided to simply remove the dependency array from the useEffect hook so that the effect handler is created and destroyed on every render/cleanup.
I've also created a Sandbox so my issue becomes more tangible.
I think this code will help you to solve your problem.
export default function App() {
const [show, setShow] = useState(false);
const selectElement = useRef(null);
const handleToggle = (e) => {
if (selectElement) {
if (!selectElement.current.contains(e.target)) {
setShow(false);
document.removeEventListener("click", handleToggle, false);
}
}
};
const handleClick = (e) => {
setShow(true)
document.addEventListener("click", handleToggle, false);
};
return (
<div className="App">
<div ref={selectElement} className="comp">
<h1 onClick={handleClick}>Select</h1>
{show && (
<div>
<div>Inner 1</div>
<div>Inner 2</div>
</div>
)}
</div>
</div>
);
}
What's up ?
I'm trying to reproduce the sliding button effect from frontity home page with ReactJS (NextJS).
Sliding buttons from Frontity
I managed to create the sliding button effect BUT I'm struggling with state management.
I have all my objects mapped with a "isActive : true/false" element and I would to create a function that put "isActive : true" on the clicked button BUT put "isActive: false" on all the other buttons.
I don't know the syntax / method for that kind of stuff.
Please, take a look at my codesandbox for more clarity (using react hooks):
https://codesandbox.io/s/busy-shirley-lgx96
Thank you very much people :)
UPDATE: As pointed out above by Drew Reese, even more cleaner/easier is to have just one activeIndex state:
const TabButtons = () => {
const [activeIndex, setActiveIndex] = useState(0);
const handleButtonClick = (index) => {
setActiveIndex(index);
};
return (
<>
<ButtonsWrapper>
{TabButtonsItems.map((item, index) => (
<div key={item.id}>
<TabButtonItem
label={item.label}
ItemOrderlist={item.id}
isActive={index === activeIndex}
onClick={() => handleButtonClick(index)}
/>
</div>
))}
<SlidingButton transformxbutton={activeIndex}></SlidingButton>
</ButtonsWrapper>
</>
);
};
I have made a slight modification of your TabButtons:
const TabButtons = () => {
const [buttonProps, setButtonProps] = useState(TabButtonsItems);
// //////////// STATE OF SLIDING BUTTON (TRANSLATE X ) ////////////
const [slidingbtn, setSlidingButton] = useState(0);
// //////////// HANDLE CLIK BUTTON ////////////
const HandleButtonState = (item, index) => {
setButtonProps((current) =>
current.map((i) => ({
...i,
isActive: item.id === i.id
}))
);
setSlidingButton(index);
};
return (
<>
<ButtonsWrapper>
{buttonProps.map((item, index) => (
<div key={item.id}>
<TabButtonItem
label={item.label}
ItemOrderlist={item.id}
isActive={item.isActive}
onClick={() => HandleButtonState(item, index)}
/>
</div>
))}
<SlidingButton transformxbutton={slidingbtn}></SlidingButton>
</ButtonsWrapper>
</>
);
};
When we click on a button, we set its isActive state to true and all the rest buttons to isActive: false. We also should use state, since we also declared it. Changing state will force component to re-render, also we are not mutating anything, but recreating state for buttons.
I am much confused as I don't know what I am doing wrong. Each time I clicked on the plus sign, all the other div elements display instead of the specific one I click on. I tried to use id argument in my show and hide functions, it is complaining of too many re-rendering . I have been on this for the past 12 hours. I need your help to solving this mystery. All I want to do is to click on the plus sign to display only the content and minus sign to hide it.
import React, {useState, useEffect} from 'react'
function Home() {
const [userData, setUserData] = useState([]);
const [showing, setShowing] = useState(false)
const [search, setSearch] = useState("");
const [clicked, setClicked] = useState("")
async function getData()
{
let response = await fetch('https://api.hatchways.io/assessment/students');
let data = await response.json();
return data;
}
useEffect(() => {
getData()
.then(
data => {
setUserData(data.students ) }
)
.catch(error => {
console.log(error);
})
}, [])
const handleFilterChange = e => {
setSearch(e.target.value)
}
function DataSearch(rows) {
const columns = rows[0] && Object.keys(rows[0]);
return rows.filter((row) =>
columns.some((column) => row[column].toString().toLowerCase().indexOf(search.toLowerCase()) > -1)
);
}
const searchPosts = DataSearch(userData);
const show = (id, e) => {
setShowing(true);
}
const hide = (id, e) => {
setShowing(false);
}
return (
<>
<div>
<input value={search} onChange={handleFilterChange} placeholder={"Search by name"} />
</div>
{
searchPosts.map((student) => (
<div key={student.id} className="holder">
<div className="images">
<img src={student.pic} alt="avatar" width="130" height="130" />
</div>
<div className="data-container">
<span className="name">{student.firstName.toUpperCase()} {student.lastName.toUpperCase()}</span>
<span>Email: {student.email}</span>
<span></span>
<span>Company: {student.company}</span>
<span>Skill: {student.skill}</span>
<span>City: {student.city}</span>
{ showing ?
<button id={student.id} onClick={hide}>-</button>
: <button id={student.id} onClick={show}>+</button>
}
<div data-id={student.id}>
{ (showing )
? student.grades.map((grade, index) => (
<span id={index} key={index}>Test {index}: {grade}%</span>
)) : <span>
</span>
}
</div>
</div>
</div>
))
}
</>
)
}
export default Home
Change,
const [showing, setShowing] = useState(false)
to:
const [showing, setShowing] = useState({});
Here change the useState from boolean to object.. Reason for this is we will store the ids as keys and a boolean value indicating if the grade should be shown or not.
And remove Show and hide function and have a common toggle function like,
const toggleGrades = (id) => {
setShowing((previousState) => ({
...previousState,
[id]: !previousState[id]
}));
};
You are using setShowing(true) in show function and setShowing(false) in hide function which is the reason for opening all and closing all at any click.. Because you have never mentioned which exact grade should be shown so you need to make use of id here..
And buttons click handler will be like,
{showing[student.id] ? (
<button id={student.id} onClick={() => toggleGrades(student.id)}>
-
</button>
) : (
<button id={student.id} onClick={() => toggleGrades(student.id)}>
+
</button>
)}
So pass student id () => toggleGrades(student.id) in both show and hide button an make the button gets toggled.
Display the grades like,
<div data-id={student.id}>
{showing[student.id] ? (
student.grades.map((grade, index) => (
<span id={index} key={index}>
Test {index}: {grade}%
</span>
))
) : (
<span></span>
)}
</div>
Here if showing[student.id] will display only the grades of clicked item.
And that is why id plays a major role in such case.
Working Example:
I'm constructing an input field in React that looks like so:
When the 'x' is clicked (StyledCloseCircle), the text will be cleared, and the 'x' symbol should disappear. The 'x' symbol is currently shown with javascript when the input field is focused,
export const Search = React.forwardRef((props, ref) => {
const [isFocused, setFocus] = useState(false);
const [isHovered, setHover] = useState(false);
return (
<InputContainer
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
<StyledInput
onFocus={() => setFocus(true)}
onBlur={() => setFocus(false)}
isHovered={isHovered}
ref={ref}
{...props}
/>
{isFocused && !props.value && (
<StyledMagnifyingGlass
isHovered={isHovered}
isFocused={isFocused}
onClick={props.onSearch}
/>
)}
{isFocused && props.value && (
<StyledCloseCircle onClick={() => console.log("THIS DOES NOT FIRE")} />
)}
{!isFocused && (
<StyledMagnifyingGlass
isHovered={isHovered}
isFocused={isFocused}
onClick={props.onSearch}
/>
)}
</InputContainer>
);
});
The issue is that when the 'x' is clicked, the input looses focus, which causes the 'x' to be removed on the next render, and does not register the click event. It does, however, trigger the mousedown event.
Therefore, my two questions are:
What is the order of operations when the 'x' is clicked, that leads it to registering mousedown but not click?
How can I achieve the desired behavior?
You should create a separate state to control where to show/hide the Clear button. Show it onFocus even as you do now but hide it if user clicks outside of the input container or if clicks on the Clear button. You can additionally hide it onBlur but with some timeout (500-1000ms) in case if user uses keyboard instead of a mouse.
This is a CodeSnadbox example of the code below:
function App() {
const inputContainerRef = useRef();
const [value, setValue] = useState("");
const [showClear, setShowClear] = useState(false);
const onFocus = useCallback(() => {
setShowClear(true);
}, []);
const onClear = useCallback(() => {
setValue("");
setShowClear(false);
}, []);
const onOutsideClick = useCallback(e => {
// hide Clear button only if clicked outside of the container
if (!inputContainerRef.current.contains(e.target)) {
setShowClear(false);
}
}, []);
useLayoutEffect(
() => {
// set the listener only if we shown the Clear button and remove the listener once we hid it
if (showClear) {
document.addEventListener("click", onOutsideClick);
return () => document.removeEventListener("click", onOutsideClick);
}
},
[showClear] // re-invoke if the state changes
);
return (
<div className="App">
<div className="input-container" ref={inputContainerRef}>
<input
value={value}
onChange={e => {
setValue(e.target.value);
}}
className="input"
type="tetxt"
onFocus={onFocus}
/>
{showClear && (
<div className="clear" onClick={onClear}>
X
</div>
)}
</div>
</div>
);
}