How to dynamically map an array within another array in React? - javascript

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

Related

React how to call function depends on specific element id

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!

Dropdown filter using React

I am currently stuck on creating this dropdown filter functionality in React. I have this array here that I have in a solutions.json file:
[
{"name": "Employer Branding", "id": "415"},
{"name": "Account Based Marketing", "id": "414"},
{"name": "Thought Leadership", "id": "413"}
]
I want to filter out the case studies 'solutions' that I have displaying dynamically through a REST API. I currently have it showing ALL case studies. This is my code:
<div className={classes.cardContainer}>
<ul className="ulGrid">
{caseStudies.map((result) => {
return (
<li className={classes.liGrid} key={result.id}>
<LayeredCard
href={`/case-studies/${result.slug}`}
key={result.id}
title={result.title.rendered}
icon={result.acf.icon}
subtitle={result.acf.case_study_pre_heading}
readMore="Read More"
></LayeredCard>
</li>
);
})}
</ul>
</div>
I want to be able to filter these case studies through their solutions category and make it a condition that if the "solutions" ids match then render filtering result.
API data from a case study
So far I have created a dropdown filter without it actually filtering in a dropdown.js file:
import { useState, useRef, useEffect } from "react";
export default function Dropdown({
options,
prompt,
value,
onChange,
id,
label,
}) {
const [open, setOpen] = useState(false);
const ref = useRef(null);
//tracking when the dropdown is clicked
useEffect(() => {
document.addEventListener("click", close); //when click happens we call the close function
return () => document.removeEventListener("click", close);
}, []);
// (&& logical AND operator), if our dropdown is open and we click outside of the dropdown than the e.target is the
// html document which will NOT be equal to ref.current (our dropdown) which will close the dropdown as a result
function close(e) {
setOpen(e && e.target === ref.current);
}
return (
<div className={classes.dropdown}>
<div className={classes.control} onClick={() => setOpen((prev) => !prev)}>
<div className={classes.selectedValue} ref={ref}>
{/* if there is a value clicked then that shows if not the prompt shows */}
{value ? value[label] : prompt}
</div>
<div
className={`${classes.arrow} ${open ? `${classes.open}` : null} `}
></div>
<div
className={`${classes.options} ${open ? `${classes.open}` : null} `}
>
{/* className={classes.arrow open ? classes.open} */}
{options.map((option) => (
//the selected value is indicated with a css style
<div
key={option[id]}
className={`${classes.option} ${
value === option ? `${classes.selected}` : null
} `}
onClick={() => {
onChange(option); //change this
setOpen(true); //when you click the option the dropdown then closes.
}}
>
{option[label]}
</div>
))}
</div>
</div>
</div>
);
}
I am calling the Dropdown function in my casestudies.js file like so:
import solutions from "../data/solutions.json";
import Dropdown from "../components/dropdown/filterDropdown/Dropdown";
<Dropdown
options={solutions}
prompt="Solutions"
id="id"
label="name"
value={value}
onChange={(val) => setValue(val)}
/>
<div className={classes.cardContainer}>
<ul className="ulGrid">
{caseStudies.map((result) => {
return (
<li className={classes.liGrid} key={result.id}>
<LayeredCard
href={`/case-studies/${result.slug}`}
key={result.id}
title={result.title.rendered}
icon={result.acf.icon}
subtitle={result.acf.case_study_pre_heading}
readMore="Read More"
></LayeredCard>
</li>
);
})}
</ul>
</div>
Not sure how to render the filtering result. Can anyone help?

Cannot access updated value

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>

Dynamic way to handle conditional render

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

Show All Button - JSX Logic

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

Categories

Resources