how I can tigress an function depends on id of an element. Right now all elements getting clicked if I click on any single element. how to prevent to show all element ? here is my code
const[showsubcat,setShowSubCat] = useState(false)
let subcategory=(()=>{
setShowSubCat(prev=>!prev)
})
my jsx
{data.map((data)=>{
return(
<>
<li class="list-group-item" id={data.id} onClick={subcategory} >{data.main_category}</li>
{showsubcat &&
<li><i class="las la-angle-right" id="sub_category"></i> {data.sub_category}</li>
}
</>
)
see the screenshot. I am clicking on single items but it's showing all items.
Every li should have it own state
so it's either you create states based on number of li if they're just 2 elements max! but it's ugly and when you want to add more li it's gonna be a mess
so you just create a component defining the ListItem and every component has it own state.
function ListItem({data}) {
const[showsubcat,setShowSubCat] = useState(false)
const subcategory= ()=> setShowSubCat(prev=>!prev)
return (
<>
<li class="list-group-item" id={data.id} onClick={subcategory} >
{data.main_category}
</li>
{showsubcat &&
<li>
<i class="las la-angle-right" id="sub_category"></i>
{data.sub_category}
</li>
}
</>
)
}
and you use it in the list component like this
data.map((datum, index) => <ListItem key={index} data={datum} />
EDIT AFTER THE POST UPDATE (misunderstanding)
the list item (or the block containing the li and the helper text) should be an independant component to manage it own state
function PostAds(data) => {
return (
<>
{
data.map((data, index) => <ListItem key={index} data {data}/>
}
</>
)
}
function ListItem({data}) {
const [showsubcat, setShowSubCat] = useState(false)
const subcategory = () => setShowSubCat(prev => !prev)
return (
<>
<li
class="list-group-item"
id={data.id}
onClick={subcategory}>
{data.main_category}
</li>
{
showsubcat &&
<li >
<i class = "las la-angle-right" id = "sub_category"></i>
{data.sub_category}
</li>
}
</>
)
}
The reason this is happening is because you are using the same variable showsubcat to check if the category was clicked or not.
A proper way to do this would be by either making showsubcat as an array that holds ids of those categories that were clicked like:
const[showsubcat,setShowSubCat] = useState([])
let subcategory=((categoryId)=>
showsubcat.includes(categoryId) ?
setShowSubCat(showsubcat.filter(el => el !== categoryId)) :
setShowSubCat([...showsubcat, categoryId]));
and then while mapping the data:
{data.map((category)=>
(
<>
<li class="list-group-item" id={category.id} key={category.id}
onClick={() => subcategory(category.id)}>
{category.main_category}
</li>
{showsubcat.includes(category.id) &&
<li>
<i class="las la-angle-right"
id="sub_category" key={`subCategory${category.id}`} />
{category.sub_category}
</li>
}
</>
)
}
The other method would be to add a new key in your data array as selectedCategory and change its value to true/false based on the click, but this is a bit lengthy, let me know if you still want to know that process.
Also, accept the answer if it helps!
Related
Hello Am i need of some assistance here am stuck,tried to serach for solution on SO but cant find a solution ,Am learning react so decided to create a todo app.However i have been stuck when it comes to crossing off completed tasks. when i add a task i have a variable called tasks which basically is an object containing all the tasks in the following fomart:
enter image description here
Inside my app i have the following snippet of code
const FILTER_MAP = {
all_items:() => true,
Active: task => !task.completed,
Completed: task => task.completed
};
const FILTER_NAMES = Object.keys(FILTER_MAP);
function App(props) {
const [tasks, setTasks] = useState(props.tasks);
const [filter, setFilter] = useState('all_items');
function addTask(name) {
const newTask = {id: "todo-" + nanoid(),name: name, completed:false}
setTasks([...tasks, newTask]);
}
function toggleTaskCompleted(id) {
const updatedTasks = tasks.map(task => {
// if this task has the same ID as the edited task
if(id === task.id) {
// use object spread to make a new object
// whose `completed` prop has been inverted
return {...task,completed: !task.completed}
}
return task;
})
setTasks(updatedTasks);
}
function clearCompletedTasks(){
const completed = tasks.filter(task => task.completed === false)
setTasks(completed);
}
const taskList = tasks
.filter(FILTER_MAP[filter])
.map(task => (
<Todo
id={task.id}
name={task.name}
completed={task.completed}
key={task.id}
toggleTaskCompleted={toggleTaskCompleted}
clearCompletedTasks={clearCompletedTasks}
/>
));
const filterList = FILTER_NAMES.map(name => (
<FilterButton
key={name}
name={name}
isPressed={name === filter}
setFilter={setFilter}
/>
));
const tasksNoun = taskList.length !== 1 ? 'items' : 'items';
const headingText = `${taskList.length} ${tasksNoun} left`;
return (
<div>
<header>
<h1 id="pageTitle">Todo</h1>
<div className="container">
<div className="main">
<section className="tasklist">
<Form addTask={addTask}/>
<ul className="listItems">
<li className={`todo-item ${ tasks.completed ? "completed" :'' }`} >{taskList}</li>
</ul>
</section>
</div>
<div className="footer">
<div className="footer-list">
<ul>
<li id="items">{headingText}</li>
<li id="all-items">{filterList[0]}</li>
<li id="active">{filterList[1]}</li>
<li id="completed">{filterList[2]}</li>
<li id="clear" onClick={clearCompletedTasks}>XClear Completed</li>
</ul>
</div>
</div>
</div>
</header>
</div>
);
}
export default App;
Todo
export default function Todo(props){
return (
<li>
<div className="todo">
<label htmlFor={props.id}>
{props.name}
</label>
<input id={props.id}
type="checkbox"
defaultChecked={props.completed}
onChange={() =>
props.toggleTaskCompleted(props.id)}
/>
</div>
</li>
);
}
Problem
When i click on checkbox to indicate the task is done i can see that the value in completed is updating to true as show below
enter image description here
However when i try to evaluate and apply the following css its not working.
<li className={`todo-item ${ tasks.completed ? "completed" :'' }`} >{taskList}</li>
How can i implement this so that "completed" style class is used when the state of completed value changes to true.Thank you
The problem is that you use a const to store an array which gets changed.
The correct thing to do would be the following:
<ul>{tasks
.filter(FILTER_MAP[filter])
.map(task => (
<li class={`todo-item ${ task.completed ? "completed" :'' }`}>
<Todo
id={task.id}
name={task.name}
completed={task.completed}
key={task.id}
toggleTaskCompleted={toggleTaskCompleted}
clearCompletedTasks={clearCompletedTasks}
/></li>
))}
</ul>
This looks like a case of stale state. When your function closes over an old state (think closures), then whenever called in future it has access to the old state itself. Similar question
Check if moving your {tasksList} code inside the return method helps:
<li className={`todo-item ${ tasks.completed ? "completed" :'' }`} >{
tasks
.filter(FILTER_MAP[filter])
.map(task => (
<Todo
id={task.id}
name={task.name}
completed={task.completed}
key={task.id}
toggleTaskCompleted={toggleTaskCompleted}
clearCompletedTasks={clearCompletedTasks}
/>
));}</li>
Coming from jQuery into the world of React, i'm working on a problem that's asking to display details from a JSON response using a show/hide button. My current implementation just has me using CSS to show/hide a sibling div. Is there a way to instead dynamically render the specific component instead of loading all of them on the page and using CSS to control their display?
Component:
<ul>
{countries.map(country =>
<li key={country.Countriesalpha2Code}>
{country.name} <button onClick={showDetails}>show</button>
<div style={{display: 'none'}}>
<Details country={country} />
</div>
</li>
)}
</ul>
Function:
const showDetails = (event) => {
let target = event.target
let sibling = target.nextSibling
if(sibling.style.display == 'none'){
sibling.style.display = 'block'
} else {
sibling.style.display = 'none'
}
if(target.textContent == 'show') {
target.textContent = 'hide'
} else {
target.textContent = 'show'
}
}
You definitely shouldn't be modifying element states in callbacks. Instead, you could make the expanded/hidden state of each element a state atom, like so:
const CountryDetail = ({ country }) => {
const [expanded, setExpanded] = React.useState(false);
const toggleExpanded = React.useCallback(() => setExpanded((expanded) => !expanded), []);
return (
<li>
{country.name}
<button onClick={toggleExpanded}>show</button>
{expanded ? <Details country={country} /> : null}
</li>
);
};
const Countries = () => (
<ul>
{countries.map((country) => (
<CountryDetail key={country.Countriesalpha2Code} country={country} />
))}
</ul>
);
I'm trying to develop a button that once I click it I show all the items available on my shopping list. But I'm struggling to put all the pieces together and come up with the correct syntax.
I have an array with 20 items and I would like to have initially only 15 (I'm assuming I'd have to use useState there?) displayed on the screen. Once I'd click the button it would show all the items on my shopping list array. Can any body help me to structure this feature? Thanks :)
const showAll = useCallback(() => {
const availableItems = items;
if (availableItems > 15) {
//STRUGGLING
}
}, []);
return (
<div className="items.container">
<ul className="shoplist-items">
{items.map((item, i) => {
return (
<li className="items">
<div className="single-item" key={i}>
{item}
</div>
</li>
);
})}
</ul>
<div className="show-all-container">
<p onClick={showAll}>Show all</p>
</div>
</div>
);
Use useState hook from react, init your avaible items only with 15 elements to show, and map avaibleItems instead items, and onClick just set avaibleItems:
const [avaibleItems, setAvaibleItems] = useState(items.slice(0,15);
const showAll = () =>{ setAvaibleItems(items)}
const allItems = useRef(items);
const [visibleItems, setVisibleItems] = useState(allItems.current.slice(0,15));
const showAll = useCallback(() => {
if(allItems.current.length > 15) {
setVisibleItems(allItems.current);
}
}
return (
<div className="items.container">
<ul className="shoplist-items">
{visibleItems.map((item, i) => {
return (
<li className="items">
<div className="single-item" key={i}>
{item}
</div>
</li>
);
})}
</ul>
<div className="show-all-container">
<p onClick={showAll}>Show all</p>
</div>
</div>
);
I'm building a table of content using React. I'm calling my database to fetch each array(which are always different depending on query). I would like to render each child array when I click on the parent item. Here's conceptually what I want:
<ul id="parent" onClick={renderChildArray()}>
<li id="child" onClick={renderChild2Array()}>
{child2array}
<li>
</ul>
Here's my code:
tableOfContent = () => {
const { TOC, headers2 } = this.state;
return (
<div>
{TOC.map((header) => (
<ul
key={header.index}
onClick={() =>
this.handleHeaderClick(
header.level,
header.treepath,
header.containsLaw,
header.sections,
header.ObjectId,
)
}
className="TOC TOCsection"
>
{header._id}
{headers2.map((i, index) => (
<li
className="TOCsection"
style={{ listStyle: "none" }}
key={index}
>
{i._id}
</li>
))}
</ul>
))}
</div>
);
};
Right now, when I click on the parent the child appears on each parent key item. I want the child array to render under the parent that I clicked only. How to do that?
You can save the clicked parent's index in the state. And when rendering child items check if the current parentIndex === saveIndex and then render the child. I can write the pseudocode for this as I don't have a working version of your problem.
tableOfContent = () => {
const { TOC, headers2 } = this.state;
return (
<div>
{TOC.map((header, parentIndex) => (
<ul
key={header.index}
onClick={() =>
this.handleHeaderClick(
header.level,
header.treepath,
header.containsLaw,
header.sections,
header.ObjectId,
);
saveTheIndex(parentIndex); // This method should save parentIndex in the state. I am assuming the state variable is named 'clickedParentIndex'.
}
className="TOC TOCsection"
>
{header._id}
{ clickedParentIndex === parentIndex && headers2.map((i, index) => (
<li
className="TOCsection"
style={{ listStyle: "none" }}
key={index}
>
{i._id}
</li>
))}
</ul>
))}
</div>
);
};
Ok so I am using express-react-views as a templating engine and I am currently trying to make a working breadcrumb. On each route I pass a prop called "crumb" that is an array of the current location on the app. That array looks like this:
[
{
text: "Home",
href:"/",
active: false
},
{
text: "Step2",
href:`/page`,
active: true
}
]
Obviously this can be multiple steps down. The last step is the page you are on, so active is set to true. This is where my problem is. To render this on the page I am mapping this array to JSX like this:
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.map(crumb =>
<li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li>
)}
</ol>
)
}
This code works fine but what the active page should have the class "active" on it and should not have an "a" tag. So what I need to do it as it's mapping this array to check for the active:true value and then map a different element. I hope that makes sense.
Hi you can try this out if you want both active and inactive links to be shown:
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.map(crumb =>
crumb.active ? <li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li> : <li class="breadcrumb-item">{crumb.text}</li>
)}
</ol>
)
}
if you only want to show active links then you can use:
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.filter(item => item.active).map(crumb =>
<li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li>
)}
</ol>
)
}
Inside map you can check crumb.active, so it will either return true or false and based on that you can return respective element.
Is this what you want
const Breadcrumb = props => {
return (
<ol class="breadcrumb">
{props.crumb.map(crumb => {
if(crumb.active)
return <li class="breadcrumb-item active"></li>
else
return <li class="breadcrumb-item"><a href={crumb.href}>{crumb.text}</a></li>
})}
</ol>
)
}