I have cards with categories, clicking on which opens hidden content. I need the card to close when switching between them and if a click occurs behind the content.
import React, { useRef } from "react";
import s from "./Shop.module.css";
import { useState } from "react";
export const Shop = () => {
const card = [
{
name: "Brands",
cat: ["Adidas", "Nike", "Reebok", "Puma", "Vans", "New Balance"],
show: false,
id: 0,
},
{
name: "Size",
cat: ["43", "43,5"],
show: false,
id: 1,
},
{
name: "Type",
cat: ["Sneakers ", "Slippers"],
show: false,
id: 2,
},
];
const [active, setActive] = useState({});
const handleActive = (id) => {
setActive({ ...active, [id]: !active[id] });
};
const handleDisable = (index) => {
setActive(index);
};
return (
<div className={s.container}>
<div className={s.brandInner}>
{card.map((i, index) => {
return (
<div className={s.brandCard} key={i.id}>
<button
className={`${s.brandBtn} `}
onClick={() => handleActive(i.id, index)}
onBlur={() => handleDisable(index)}
>
<p className={`${active[i.id] ? `${s.brandBtnActive}` : ``}`}>
{i.name}
</p>
</button>
<div
className={`${s.openCard} ${active[i.id] ? "" : `${s.dNone}`}`}
>
<ul className={s.brandList}>
{i.cat.map((elem) => {
return (
<li key={elem} className={s.brandItem}>
{elem}
</li>
);
})}
</ul>
<button className={s.brandOpenBtn}>Apply</button>
</div>
</div>
);
})}
</div>
</div>
);
};
I tried to do it through onBlur, but this way I can't interact with the content that appears when opening the card, please help
You could do this a few different ways, here are two.
Array version
You can do this by using a array to keep track of the ids that are active.
const [active, setActive] = useState([]);
For the event handler we will creata a new toggleActive function which replaces the others. This will check if the id is already in the array and if so remove it, else add it.
const toggleActive = (id) => {
setActive((prevActive) => {
if (prevActive.includes(id)) {
return prevActive.filter((activeId) => activeId !== id);
}
return [...prevActive, id];
});
};
Then in the return of the component we need to updated some logic as well. Then handlers only take in the id of the i. To check if the id is in the array with can use includes.
<button
className={s.brandBtn}
onClick={() => toggleActive(i.id)}
>
<p className={`${active.includes(i.id) ? s.brandBtnActive : ""}`}>
{i.name}
</p>
</button>
<div
className={`${s.openCard} ${active.includes(i.id) ? "" : s.dNone}`}
>
Object version
This version is to do it with a object.
const [active, setActive] = useState({});
The handler, this will toggle the value of the id starting with false if there is no value for the id yet.
const toggleActive = (id) => {
setActive((prevActive) => {
const prevValue = prevActive[id] ?? false;
return {
...prevActive,
[id]: !prevValue,
};
});
};
The elements
<button
className={s.brandBtn}
onClick={() => toggleActive(i.id)}
>
<p className={`${active[i.id] ? s.brandBtnActive : ""}`}>
{i.name}
</p>
</button>
<div
className={`${s.openCard} ${active[i.id] ? "" : s.dNone}`}
>
Edit: toggle with closing others
First we declare the state with a initial value of null
const [active, setActive] = useState(null)
We create the toggleActive function which checks if the id to toggle is the previous id, if so return null else return the new active id
const toggleActive = (id) => {
setActive((prevActive) => {
if (prevActive === id) return null;
return id;
});
};
For the rendering it is quite simple, add the toggleActive function to the button and check if the active is the same id
<button
className={s.brandBtn}
onClick={() => toggleActive(i.id)}
>
<p className={`${active === i.id ? s.brandBtnActive : ""}`}>
{i.name}
</p>
</button>
<div
className={`${s.openCard} ${active === i.id ? "" : s.dNone}`}
>
Related
Trying to toggle a line-through in jsx when clicking on an item in an app I'm building. The line appears when clicked but I want to remove the line when clicked a second time. Here's my code so far
import { useState } from "react";
function ToDo() {
const [toDoInput, setToDoInput] = useState("");
const [toDoList, setToDoList] = useState([
{ text: "Read a Book", completed: false },
{ text: "Play Apex Legends", completed: false },
{ text: "Bake a Cake", completed: false },
]);
// Add a todo
const addToDo = () => {
if (!toDoInput) return;
const newList = [...toDoList];
newList.push({ text: toDoInput, completed: false });
setToDoList(newList);
setToDoInput("");
};
// delete a todo
const deleteToDo = (index) => {
const newArray = [...toDoList];
newArray.splice(index, 1);
setToDoList(newArray);
};
// toggle if a todo has been completed or not
const toggleToDo = (index) => {
const newArray = [...toDoList];
newArray[index].completed = !newArray[index.completed];
setToDoList(newArray);
};
return (
<div>
<h2>To Do App</h2>
<input
value={toDoInput}
onChange={(e) => setToDoInput(e.target.value)}
></input>
<button onClick={addToDo}>Add To Do</button>
<ul>
{toDoList.map((toDo, key) => {
return (
<li
key={key}
style={{ textDecoration: toDo.completed && "line-through" }}
>
<span onClick={() => toggleToDo(key)}>{toDo.text}</span>
<button onClick={() => deleteToDo(key)}>x</button>
</li>
);
})}
</ul>
</div>
);
}
export default ToDo;
I tried to write code that would allow me to toggle a line-through
I'm trying to make a simple todo in react. I want to be able to click in the button next to the todo text and mark it as complete, with a line passing through it, so I guess the point of the button would be to toggle between the two stylings. But I don't know how to apply the styling to that specific todo. Here's my code so far:
import React, { useState } from 'react';
function App() {
const [todos, setTodos] = useState([])
const toggleComplete = (i) => {
setTodos(todos.map((todo, k) => k === i ? {
...todo, complete: !todo.complete
} : todo))
}
const handleSubmit = (event) => {
event.preventDefault()
const todo = event.target[0].value
setTodos((prevTodos) => {
return [...prevTodos, {
userTodo: todo, completed: false, id: Math.random().toString()
}]
})
}
return (
<div>
<form onSubmit={handleSubmit}>
<input placeholder='name'></input>
<button type='submit'>submit</button>
</form>
<ul>
{todos.map((todos) => <li key={todos.id}>
<h4>{
todos.completed ? <s><h4>{todos.userTodo}</h4></s> : <h4>{todos.userTodo}</h4>}
</h4>
<button onClick={toggleComplete}>Mark as complete</button>
</li>)}
</ul>
</div>
);
}
export default App;
You can see that the toggleComplete function takes a parameter i which is the id of the todo, so you should call it like onClick={() => toggleComplete(todos.id)}.
However this still didn't work since you are assigning random numbers as strings as id to the todos then iterating over the array.
As Alex pointed out, there's a bug in your code regarding the completed toggle, so I fixed it and here's a working version of the code you can take a look at and improve:
import React, { useState } from "react";
export default function App() {
const [todos, setTodos] = useState([]);
const toggleComplete = (i) => {
setTodos(
todos.map((todo, k) => {
return k === i
? {
...todo,
completed: !todo.completed
}
: todo;
})
);
};
const handleSubmit = (event) => {
event.preventDefault();
const todo = event.target[0].value;
setTodos((prevTodos) => {
return [
...prevTodos,
{
userTodo: todo,
completed: false,
id: prevTodos.length
}
];
});
};
return (
<div>
<form onSubmit={handleSubmit}>
<input placeholder="name"></input>
<button type="submit">submit</button>
</form>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.completed ? (
<s>
<p>{todo.userTodo}</p>
</s>
) : (
<p>{todo.userTodo}</p>
)}
<button onClick={() => toggleComplete(todo.id)}>
Mark as complete
</button>
</li>
))}
</ul>
</div>
);
}
There are 2 problems in your code as i see:
typo in the toggleComplete function
Fix: the following code complete: !todo.complete shopuld be completed: !todo.completed as this is the name of the key that you're setting below on handleSubmit.
the toggleComplete function receives as an argument the javascript event object and you are comparing it with the key here:
(todo, k) => k === i
(see more here:
https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event)
Fix: You can modify the lines of code for the todo render as follows:
{todos.map((todo, index) => <li key={todo.id}>
<React.Fragment>{
todo.completed ? <del><h4>{todo.userTodo}</h4></del> : <h4>{todo.userTodo}</h4>}
</React.Fragment>
<button onClick={() => {toggleComplete(index)}}>Mark as complete</button>
</li>)}
function Input() {
const [input, setInput] = useState("");
function handleSearch() {
let url = "https://google.com/search?q=${input}"
window.open(url)
}
return (
<div className="input-wrap">
<input
type="text"
className="input__search"
placeholder="Enter your search..."
value={input}
onChange={(e) => setInput(e.target.value)}></input>
<button
className="input__search--btn"
onClick={handleSearch}>
<i className="fa-solid fa-magnifying-glass"></i>
</button>
</div>
);
}
The search button when clicked will redirect you to a google search based on the value from the input field, below is the site for advanced search, when active the link will add an additional link after "https://google.com/search?q=${input}+site%3A${activepage}.com, how do I check if one or many sites are active then pass down its name to url
P/s: code for toggling websites
function WebButton({ icon, name }) {
const [active, setActive] = useState(false);
function handleToggle() {
setActive(!active);
}
return (
<button
className={active ? "websites-btn active" : "websites-btn"}
onClick={handleToggle}>
<i className={icon}></i>
<div className="websites-name">{name}</div>
</button>
);
}
You can keep a root level state to gather active links to a state. And pass it to the Input component.
Update your Input component to accept array called `` and update the handleSearch to use OR operation in google search.
function Input({ activeLinks }) {
const [input, setInput] = useState("");
function handleSearch() {
if (activeLinks.length > 0) {
let compundSearchURL = `https://google.com/search?q=${input}`;
activeLinks.forEach((link, i) => {
compundSearchURL += `+${i > 0 ? "OR+" : ""}site%3A${link}.com`;
});
window.open(compundSearchURL);
} else {
window.open(`https://google.com/search?q=${input}`);
}
}
return (
<div className="input-wrap">
<input
type="text"
className="input__search"
placeholder="Enter your search..."
value={input}
onChange={(e) => setInput(e.target.value)}
></input>
<button className="input__search--btn" onClick={handleSearch}>
<i className="fa-solid fa-magnifying-glass">Search</i>
</button>
</div>
);
}
Accept another function in WebButton called toggleActiveLink and a string called value which refers to the URL part. Call the function with the value inside handleToggle function.
function WebButton({ icon, name, toggleActiveLink, value }) {
const [active, setActive] = useState(false);
function handleToggle() {
setActive(!active);
toggleActiveLink(value);
}
return (
<button
className={active ? "websites-btn active" : "websites-btn"}
style={{ color: active ? "blue" : "unset" }}
onClick={handleToggle}
>
<i className={icon}></i>
<div className="websites-name">{name}</div>
</button>
);
}
In the main component you have to create a local state to handle the active links. Create the toggle function as given. It will add the value if it is not there otherwise remove it.
const urls = [
{ name: "Reddit", value: "reddit" },
{ name: "Quora", value: "quara" },
{ name: "Facebook", value: "facebook" },
{ name: "Stackoverflow", value: "stackoverflow" },
{ name: "Twitter", value: "twitter" }
];
function App() {
const [activeLinks, setActiveLinks] = useState([]);
const toggleActiveLink = (link) => {
const index = activeLinks.indexOf(link);
if (index < 0) {
setActiveLinks((prevLinks) => [...prevLinks, link]);
} else {
setActiveLinks((prevLinks) => prevLinks.filter((l) => l !== link));
}
};
return (
<>
<Input activeLinks={activeLinks} />
<div>
{urls.map(({ name, value }) => (
<WebButton
name={name}
value={value}
toggleActiveLink={toggleActiveLink}
/>
))}
</div>
</>
);
}
I have an array of job descriptions that I want to hide a part of each description and show it completely when a button is clicked using React hooks.
I am iterating over the array( consists of id and description) to show all the descriptions as a list in the component. There is a button right after each paragraph to show or hide the content.
readMore is used to hide/show the content and
activeIndex is used to keep track of clicked item index.
This is what I have done so far:
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [readMore, setReadMore] = useState(false);
const [activeIndex, setActiveIndex] = useState(null);
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{readMore ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
setActiveIndex(index);
if (activeIndex === id) {
setReadMore(!readMore);
}
}}
>
{readMore ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
The problem is that when I click one button it toggles all the items in the list.
I want to show/hide content only when its own button clicked.
Can somebody tell me what I am doing wrong?
Thanks in advance.
Your readMore state is entirely redundant and is actually causing the issue. If you know the activeIndex, then you have all the info you need about what to show and not show!
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [activeIndex, setActiveIndex] = useState(null);
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{activeIndex === index ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
if (activeIndex) {
setActiveIndex(null);
} else {
setActiveIndex(index);
}
}}
>
{activeIndex === index ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
Edit: The aforementioned solution only lets you open one item at a time. If you need multiple items, you need to maintain an accounting of all the indices that are active. I think a Set would be a perfect structure for this:
import React, { useState } from "react";
const Jobs = ({ data }) => {
const [activeIndices, setActiveIndices] = useState(new Set());
const job = data.map((job, index) => {
const { id, description } = job;
return (
<article key={id}>
<p>
{activeIndices.has(index) ? description : `${description.substring(0, 250)}...`}
<button
id={id}
onClick={() => {
const newIndices = new Set(activeIndices);
if (activeIndices.has(index)) {
newIndices.delete(index);
} else {
newIndices.add(index);
}
setActiveIndices(newIndices);
}}
>
{activeIndices.has(index) ? "show less" : "show more"}
</button>
</p>
</article>
);
});
return <div>{job}</div>;
};
export default Jobs;
Try this
{readMore && (activeIndex === id) ? description : `${description.substring(0, 250)}...`}
function Destination() {
const travels = [
{
title: "Home"
},
{
title: "Traveltype",
subItems: ["Local", "National", "International"]
},
{
title: "Contact",
subItems: ["Phone", "Mail", "Chat"]
}
];
const [activeIndex, setActiveIndex] = useState(null);
return (
<div className="menu-wrapper">
{travels.map((item, index) => {
return (
<div key={`${item.title}`}>
{item.title}
{item.subItems && (
<button
onClick={() => {
if (activeIndex) {
if (activeIndex !== index) {
setActiveIndex(index);
} else {
setActiveIndex(null);
}
} else {
setActiveIndex(index);
}
}}
>
{activeIndex === index ? `Hide` : `Expand`}
</button>
)}
{activeIndex === index && (
<ul>
{item.subItems &&
item.subItems.map((subItem) => {
return (
<li
key={`li-${item.title}-${subItem}`}
>
{subItem}
</li>
);
})}
</ul>
)}
</div>
);
})}
</div>
);
}
<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>
This is probably really easy, I want to show the info of a list item when a user clicks on the item, however, the code I have will open the info of all list items when clicked.
https://codesandbox.io/s/friendly-lichterman-j1vpd?file=/src/App.js:0-599
import React, { useState } from "react";
import "./styles.css";
const list = [1, 2, 3];
export default function App() {
const [active, setActive] = useState(false);
return (
<div>
{list.map((item, idx) => {
return (
<>
<li
onClick={() => {
setActive(!active);
}}
>
{item}
<div className={active ? "active" : "info"}>
{" "}
Info {idx + 1}
</div>
</li>
</>
);
})}
</div>
);
}
You are trying to toggle active only which is used by all items. Instead use index position to toggle.
I have explained the rest within the code using comments.
App.js
import React, { useState } from "react";
import "./styles.css";
const list = [1, 2, 3];
export default function App() {
const [active, setActive] = useState();
return (
<div>
{/* You forgot to add ul here */}
<ul>
{list.map((item, idx) => {
return (
// Added key to each child to avoid error. Use <React.Fragment/> instead of <>
<React.Fragment key={idx}>
<li
onClick={() => {
// Condition for toggling the lists.
// If current list is selected
if (active === idx) {
// change active to blank
setActive();
} else {
// change active to current index
setActive(idx);
}
}}
>
{item}
</li>
<div className={active === idx ? "info active" : "info"}>
{" "}
Info {idx + 1}
</div>
</React.Fragment>
);
})}
</ul>
</div>
);
}
Edited this css to avoid applying to another tag
style.css
.info.active {
display: flex;
}
You can try the above code on sandbox
https://codesandbox.io/s/stackoverflow-qno-65730790-tmeyf
Seems like a good use case for useReducer. It's a really useful tool to keep track of the states of multiple components. In your case you would need to track whether a given LI is active (showing information) or not. Here is how to do that with useReducer along with a Sanbdox
import React, { useState } from "react";
import "./styles.css";
const list = [1, 2, 3];
const default_states = list.map((item) => Object({ id: item, action: false }));
export default function App() {
const [li_states, dispatch] = React.useReducer((state, id) => {
return state.map((item) => {
if (item.id === id) return { id: item.id, active: !item.active };
else return item;
});
}, default_states);
return (
<div>
{list.map((item) => {
const cur = li_states.find((s) => s.id === item);
return (
<div key={item}>
<li
onClick={() => {
dispatch(item);
}}
>
{item}
</li>
<div className={cur.active ? "action" : "info"}> Info {item}</div>
</div>
);
})}
</div>
);
}
What's happening here is each time you click any of your LIs, the dispatch calls the reducer function inside React.useReducer with the ID of the LI and toggles the state of the clicked LI.
You can follow this method too
const list = ['Start', 'Installation', 'Text Banners', 'Image Banners'];
const links = ['/start', '/installation', '/textbanners', '/imagebanners'];
const [active, setActive] = useState(null)
const toggleActive = (e) => {
console.log(e)
setActive(e.target.innerText)
}
return (
<div className={style.dashboard_container}>
<div className={style.dashboard}>
<ul>
{list.map((item, index) => {
return (
<li className={active == item ? style.active : ''} key={index} onClick={toggleActive}>{item}</li>
)
})}
</ul>
</div>
{children}
</div>
);