Show/Hide div onClick - reactjs

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

Related

There are two buttons and you need to change them alternately

I have two buttons. I can change its color by clicking on one button. And when you click on another button, change its color as well, and return the old color to the first button. Something like toggle. How can I implement such functionality in a react applicatio.
const [toggle, setToggle] = useState(false);
const toggleIt = () => {
setToggle(!toggle);
};
return (
<div>
<button onClick={toggleIt}>Button1</button>
<button onClick={toggleIt}>Button2</button>
)
somthing like this (codesandbox),
import classNames from "classnames";
import { useCallback, useState } from "react";
import "./styles.css";
export default function App() {
const [toggle, setToggle] = useState(false);
const toggleIt = useCallback(() => {
setToggle((toggle) => !toggle);
}, []);
return (
<div>
<button
onClick={toggleIt}
className={classNames({
"btn-act": toggle
})}
>
Btn A
</button>
<button
onClick={toggleIt}
className={classNames({
"btn-act": !toggle
})}
>
Btn B
</button>
</div>
);
}
const [toggle, setToggle] = useState(false);
const toggleIt = () => {
setToggle(!toggle);
};
return (
<div>
<button onClick={toggleIt} style={toggle ? {color: "blue"} : {color: "red"}}</button>
<button onClick={toggleIt} style={toggle ? {color: "pink"} : {color: "purple"}}</button>
</div>
)
Background
You can use the useEffect() hook to accomplish this feature depending on the button pressed. Just hold two states and flip them each time a different button is pressed, and with those two states you can use two separate functions to handle the onClick()'s.
The useEffect() hook automatically re-renders the component once any of the items in the dependency array at the end change, which will happen depending on the button pressed.
You can also directly set true/false values on your state variables with the second value that returns from useState(), and those state variables will automatically have their states updated without you manually assigning them.
There is very likely a better, more efficient way of doing it, but this is just a general guideline, if you will.
This is the code
const [toggleOne, setToggleOne] = useState(false);
const [toggleTwo, setToggleTwo] = useState(true);
const toggleFirst = () => {
setToggleOne(true);
setToggleTwo(false);
};
const toggleSecond = () => {
setToggleOne(false);
setToggleTwo(true);
};
useEffect(() => {
if (toggleOne) {
// Do something with first button pressed
} else if (toggleTwo) {
// Do something with second button pressed
}
}, [toggleOne, toggleTwo]);
return (
<div>
<button onClick={toggleFirst}>Button1</button>
<button onClick={toggleSecond}>Button2</button>
</div>
);

React: how to use spread operator in a function that toggles states?

I made an example of my question here:
EXAMPLE
I'm mapping an array of objects that have a button that toggles on click, but when clicking on the button every object is changed.
This is the code
export default function App() {
const [toggleButton, setToggleButton] = useState(true);
// SHOW AND HIDE FUNCTION
const handleClick = () => {
setToggleButton(!toggleButton);
};
return (
<div className="App">
<h1>SONGS</h1>
<div className="container">
{/* MAPPING THE ARRAY */}
{songs.map((song) => {
return (
<div className="song-container" key={song.id}>
<h4>{song.name}</h4>
{/* ON CLICK EVENT: SHOW AND HIDE BUTTONS */}
{toggleButton ? (
<button onClick={handleClick}>PLAY</button>
) : (
<button onClick={handleClick}>STOP</button>
)}
</div>
);
})}
</div>
</div>
);
}
I know I should be using spread operator, but I couldn't get it work as I spected.
Help please!
Of course every object will change because you need to keep track of toggled state for each button. Here is one way to do it:
import { useState } from "react";
import "./styles.css";
const songs = [
{
name: "Song A",
id: "s1"
},
{
name: "Song B",
id: "s2"
},
{
name: "Song C",
id: "s3"
}
];
export default function App() {
const [toggled, setToggled] = useState([]);
const handleClick = (id) => {
setToggled(
toggled.indexOf(id) === -1
? [...toggled, id]
: toggled.filter((x) => x !== id)
);
};
return (
<div className="App">
<h1>SONGS</h1>
<div className="container">
{songs.map((song) => {
return (
<div className="song-container" key={song.id}>
<h4>{song.name}</h4>
{toggled.indexOf(song.id) === -1 ? (
<button onClick={() => handleClick(song.id)}>PLAY</button>
) : (
<button onClick={() => handleClick(song.id)}>STOP</button>
)}
</div>
);
})}
</div>
</div>
);
}
There are many ways to do it. Here, if an id is in the array it means that button was toggled.
You can also keep ids of toggled buttons in object for faster lookup.
One way of handling this requirement is to hold local data into states within the Component itself.
I have created a new Button Component and manages the toggling effect there only. I have lifted the state and handleClick method to Button component where it makes more sense.
const Button = () => {
const [toggleButton, setToggleButton] = useState(true);
const click = () => {
setToggleButton((prevValue) => !prevValue);
};
return <button onClick={click}>{toggleButton ? "Play" : "Stop"}</button>;
};
Working Example - Codesandbox Link

Drop down for a single element in a loop based on an id in react

I'm new to react and have an app that displays some data. I am using a map function to build one component multiple times. When a button is clicked inside of an element more data should be displayed but only in the clicked element. Currently, when I click a button in one element can toggle the display of the additional data for all element as well as store the unique id of the clicked element in a state. I am pretty sure that I need to filter the results and I have seen similar examples but I can't say that I fully understand them. Any tips or more beginner-friendly tutorials are greatly appreciated.
import React, { useState, useEffect } from 'react';
import '../style/skeleton.css'
import '../style/style.css'
export default function Body( student ) {
const [active, setActive] = useState({
activeStudent: null,
});
const [display, setDisplay] = useState(true)
useEffect(() => {
if (display === false) {
setDisplay(true)
} else {
setDisplay(false)
}
}, [active])
const handleClick = (id) => setActive({ activeStudent: id});
return (
<div>
{student.student.map((data) => {
const id = data.id;
return (
<div key={data.id} className="row border">
<div className="two-thirds column">
<h3>{data.firstName} {data.lastName}</h3>
{ display ?
<button onClick={() => handleClick(id)}>-</button>
:
<button onClick={() => handleClick(id)}>+</button> }
{ display ? <div>
<p>{data.addional} additonal data</p>
</div> : null }
</div>
</div>
)
})}
</div>
);
}
Change your code from:
{ display ? <div><p>{data.addional} additonal data</p></div> : null }
To:
{ active.activeStudent === id ? <div><p>{data.addional} additonal data</p></div> : null }

setting states in mapped elements react

I'm having a problem setting the states of other mapped elements to false when clicking on one of the individual mapped element. For example,
const [edit, setEdit] = useState(false)
const array = ['witch-king', 'sauron', 'azog']
const arrayrow = array && array.map(el=>{
return <div>
<i
className='fal fa-edit'
onClick={(e)=>{
setEdit(true);
e.stopPropagation()
}
></i>
{edit?<i className='fal fa-times'></i>:''}
<span>{el}</span>
</div>
})
useEffect(()=>{
document.addEventListener('click', ()=>{setEdit(false)})
},[])
The issue is that when you click on one of the icons it will set the state to true, but then if you click on another element's icon, the previously clicked element's state will remain true. I want to be able to set the previously clicked element's state to false when the user clicks on another element's icon.
EDIT: here is a video further explaining what I mean
https://gyazo.com/d8123c9f9a5fcfc48b2149c7faf48bad
Maybe try this, but I am not sure if this wont cause current icon to close down.
const [edit, setEdit] = useState(false)
const array = ['witch-king', 'sauron', 'azog']
const arrayrow = array && array.map(el=>{
return <div>
<i
className='fal fa-edit'
onClick={(e)=>{
setEdit(true);
e.stopPropagation()
}
></i>
<span>{el}</span>
</div>
})
useEffect(()=>{
if(edit){
setEdit(false)
}
},[edit])
Or try something like
const [edit, setEdit] = useState(false)
const array = ['witch-king', 'sauron', 'azog']
const arrayrow = array && array.map(el=>{
const checkEl = (e, currentEl) => {
if(el === currentEl){
e.stopPropagation()
setEdit(true)
}else{
setEdit(false)
}
}
return <div>
<i
className='fal fa-edit'
onClick={(e) => checkEl(e, el)}
></i>
<span>{el}</span>
</div>
})
useEffect(()=>{
document.addEventListener('click', ()=>{setEdit(false)})
},[])
Something like that.
FULL CODE SOLUTION
const MainComponent = () => {
const [currentEdit, setCurrentEdit] = useState('')
const array = ['witch-king', 'sauron', 'azog']
const arrayrow = array && array.map(el=>(
<ChildComponent
key={el}
el={el}
currentEdit={currentEdit}
handleClick={setCurrentEdit}>
)
)
return (<> {arrayrow} Some JSX </>)
}
const ChildComponent = ({ el, handleClick, currentEdit }) => {
return (
<div>
<i
className={`fal ${(currentEdit === el) ? 'fa-times' : 'fa-edit' }`}
onClick={(e) => {
handleClick(el)
}
></i>
<span>{el}</span>
</div>
)
}
EXPLANATION
Instead of using a boolean, I used the value of each item to represent the currently edited item, and initially, it is set to an empty string like this
const [currentEdit, setCurrentEdit] = useState('')
I made a child component passing each the currentEdit and the setCurrentEdit as props.
<ChildComponent
key={el}
el={el}
currentEdit={currentEdit}
handleClick={setCurrentEdit}>
)
Then do a check inside the child component and reset the currentEdit to that element on click
<i
className={`fal ${(currentEdit === el) ? 'fa-times' : 'fa-edit' }`}
onClick={(e) => {
handleClick(el)
}
></i>
PS: Remove the useEffect with the event listener, adding event listeners like that is an anti-pattern in React, and also you don't need it with this solution.

All buttons are clicked at the same time instead of the specific one clicked

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:

Resources