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>
Related
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!
I am wondering the best way to tackle this problem.
I intend to create a state in the parent component of my application, pass the set state to a component that holds the button (sibling A), and the value to another component where it will be displayed and updated (sibling B). I have a rough idea of how to do this, but ultimately I am lost and am seeking direction. Thank you!
My parent component is as follows
//playlist state
const [playlistItem, setPlaylistItem] = useState()
const mainCards = Data.map(card => {
return(
<MainCard
key={card.id}
id={card.id}
image={card.url}
title={card.title}
playbutton={card.playbutton}
addbutton={card.addbutton}
playlistState={setPlaylistItem}
/>
)
})
const sideCards = SideData.map(card => {
return(
<SideCard
image={card.sideurl}
key={card.id}
title={card.sidetitle}
playbutton={card.playbutton}
addbutton={card.addbutton}
playlistItem={playlistItem}
/>
)
})
return (
<div className="App">
{console.log("main cards" + mainCards[0])}
{console.log("side cards" + sideCards.sidetitle)}
<Navbar />
<Header />
<Playlist />
<CardContainer />
<div className="maincards">
{mainCards}
</div>
<div className="sidecards">
{sideCards}
</div>
</div>
)
}
Sibling A
const handleAdd = (id) => {
console.log(id)
}
return(
<div>
<div className="mainCardObject">
<div className="cardObj">
<img src={props.image} className ="mainCardImage"/>
<img src={props.playbutton} className="playbutton"/>
<img src={props.addbutton} onClick={() => handleAdd(props.id)} className="addbutton" />
</div>
</div>
</div>
)
}
Sibling B
function Playlist(props){
return(
<div className="playlistContainer">
<ul>
<li></li>
</ul>
</div>
)
}
You can pass both getter and setter as parameters, and use them normally in your components:
Sibling A
const handleAdd = (id) => {
//your logic
props.playlistState(prevValue => [])
}
Sibling B
return (
<div className="playlistContainer">
<ul>
<li>
{props.playlistItem}
</li>
</ul>
</div>
)
You can also create a function in your parent component, using set state, and pass this function as a parameter to your component:
Parent
const addPlayListItem = (playListItem) => {
setPlaylistItem(prev => [...prev, playListItem])
}
return (
<SiblingA
addPlayListItem={addPlayListItem}
/>
)
Sibling A
function MainCard(props) {
const handleAdd = () => {
//your logic
props.addPlayListItem({ name: 'rock', title: 'Foo Fighters', year: 2011 })
}
return (
<div>
<img src={props.addbutton} onClick={handleAdd} className="addbutton" />
</div>
)
}
Sibling A:
const handleAdd = (id) => {
props.playlistState((prev) => ([...prev, id]))
}
API: https://hn.algolia.com/api/v1/items/12701272
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import React, { useState } from "react";
// import "./index.css";
const Tree = ({ data = []}) => {
return (
<div className="d-tree">
<ul className="d-flex d-tree-container flex-column">
{data.map((tree) => (
<TreeNode node={tree} />
))}
</ul>
</div>
);
};
const TreeNode = ({ node }) => {
const [childVisible, setChildVisiblity] = useState(false);
const hasChild = node.children ? true : false;
return (
<li className="d-tree-node border-0">
<div className="d-flex" onClick={(e) => setChildVisiblity((v) => !v)}>
{hasChild && (
<div
className={`d-inline d-tree-toggler ${
childVisible ? "active" : ""
}`}
>
<FontAwesomeIcon icon="caret-right" />
</div>
)}
<div className="col d-tree-head">
<i className={`mr-1 `}> </i>
{node.text}
</div>
</div>
{hasChild && childVisible && (
<div className="d-tree-content">
<ul className="d-flex d-tree-container flex-column">
<Tree data={node.children} />
</ul>
</div>
)}
</li>
);
};
export default Tree;
I want to show all the comments in tree format,
I tried by above method but it shows error: TypeError: data.map is not a function
The data passed in tree function is the api converted into data.
What to do?
You are getting this error TypeError: data.map is not a function because data is not an array but an object. The response from the API is -
{
id:12701272,
created_at:"2016-10-13T15:15:48.000Z",
created_at_i:1476371748,
type:"story",
author:"fatihky",
title:"Google's “Director of Engineering” Hiring Test",
url:"http://www.gwan.com/blog/20160405.html",
text:null,
points:1764,
parent_id:null,
story_id:null,
children:(190) [...],
options:(0) [...]
}
The comment is inside the the object which is inside the array having children as the key.
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>
);