I'm beginner and I'm working on Todo app in "React". I made a class with text-decoration: line-through and I want to cross out text when click on check_btn. But I don't know why the text won't cross out.
const Task = ({ setTasks, tasks, id, title }) => {
const deleteHandler = () => {
setTasks(tasks.filter((el) => el.id !== id));
};
const completeHandler = () => {
setTasks(
tasks.filter((item) => {
if (item.id === tasks.id) {
return {
...item,
completed: !item.completed,
};
}
return item;
})
);
};
return (
<div>
<li>
<div className={`task_name ${tasks.completed ? "completed " : ""}`}>
{title}
</div>
<button className="delete_btn" onClick={deleteHandler}>
<FaTimesCircle className="delete_item" />
</button>
<button className="edit_btn">
<FaEdit className="edit_item" />
</button>
<button className="check_btn" onClick={completeHandler}>
<FaCheckCircle className="check_item" />
</button>
</li>
</div>
);
};
tasks.completed will always be undefined. tasks is the array of tasks, not an individual task inside that array.
What you want to do is check the value of task.completed for each task in the tasks array.
You should use map instead of filter in completeHandler
filter is to filter data conditionally
map is to map a current object to another object
In your case, you change completed attribute in existing tasks means mapping data instead of filtering data.
Another problem is you use condition if (item.id === tasks.id) wrongly. It should be if (item.id === id) which is comparing to the current task id.
const Task = ({ setTasks, tasks, id, title }) => {
//find the current task by task id
const currentTask = tasks.find(task => task.id === id)
//if cannot find any task by task id in the current tasks list
//return nothing
if(!currentTask) {
return null
}
const deleteHandler = () => {
setTasks(tasks.filter((el) => el.id !== id));
};
const completeHandler = () => {
setTasks(
//use `map` instead of `filter`
tasks.map((item) => {
//compare to the current task id instead of `tasks.id` (not existing)
if (item.id === id) {
return {
...item,
completed: !item.completed,
};
}
return item;
})
);
};
return (
<div>
<li>
<div className={`task_name ${currentTask.completed ? "completed " : ""}`}>
{title}
</div>
<button className="delete_btn" onClick={deleteHandler}>
<FaTimesCircle className="delete_item" />
</button>
<button className="edit_btn">
<FaEdit className="edit_item" />
</button>
<button className="check_btn" onClick={completeHandler}>
<FaCheckCircle className="check_item" />
</button>
</li>
</div>
);
};
Related
I seem to be getting the error "filtered.map is not a function" and I'm assuming its because of my onSnapshot returning a null because it hasn't fired yet to pull the data, (aka make it available) to be used to filter inside my component.
If I change the .map from filtered useState to my useState knives then I can get the data and no error and when I change it back I can then use my filter component without any issues.
Parent Component:
function KnifesComponent() {
const knifeCollection = collection(db, "knives");
const [knives, setKnives] = useState([]);
const [count, setCount] = useState(0);
const [filtered, setFiltered] = useState([]);
const [activeFilter, setActiveFilter] = useState("");
useEffect(() => {
const sub = onSnapshot(knifeCollection, (snapshot) => {
setKnives(snapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
setFiltered(snapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
const TotalSkins = snapshot.size;
setCount(TotalSkins);
});
return () => {
sub();
}
}, []);
const rarities = {
Exclusive: "Rarity_Exclusive.png",
Ultra: "Rarity_Ultra.png",
Premium: "Rarity_Premium.png",
Deluxe: "Rarity_Deluxe.png",
};
return (
<div className="collection">
<h2 className="section-heading">
Knives <div className="count">({count})</div>
<Filter
skins={knives}
setFiltered={setFiltered}
activeFilter={activeFilter}
setActiveFilter={setActiveFilter}
/>
</h2>
{filtered.map((skin) => {
return (
<div
layout
animate={{ opacity: 1 }}
initial={{ opacity: 0 }}
exit={{ opacity: 0 }}
className="skin-box"
key={skin.id}
>
<div className="skin-rarity">
<Image src={require(`../../public/${rarities[skin.rarity]}`)} />
</div>
<h4>{skin.name}</h4>
<div className="skin-value">
<p>{skin.value}</p>
</div>
</div>
);
})}
<div className="clearfix"></div>
</div>
);
}
Filter Component:
function Filter({ setActiveFilter, activeFilter, setFiltered, skins }) {
useEffect(() => {
if (activeFilter === "") {
setFiltered("");
return;
}
const filtered = skins.filter((skin) => skin.rarity.includes(activeFilter));
console.log(filtered)
setFiltered(filtered);
}, [activeFilter]);
return (
<div className="filter-container">
<button
className={activeFilter === [] ? "active" : ""}
onClick={() => setActiveFilter([])}
>
All
</button>
<button
className={activeFilter === "Ultra" ? "active ultra" : "ultra"}
onClick={() => setActiveFilter("Ultra")}
>
Ultra
</button>
<button
className={
activeFilter === "Exclusive" ? "active exclusive" : "exclusive"
}
onClick={() => setActiveFilter("Exclusive")}
>
Exclusive
</button>
<button
className={activeFilter === "Premium" ? "active premium" : "premium"}
onClick={() => setActiveFilter("Premium")}
>
Premium
</button>
<button
className={activeFilter === "Deluxe" ? "active deluxe" : "deluxe"}
onClick={() => setActiveFilter("Deluxe")}
>
Deluxe
</button>
</div>
);
}
What am I doing wrong or what do I need to change to always have the array filled for the filter to work?
This is one source of problems:
if (activeFilter === "") {
setFiltered("");
return;
}
You're setting the filtered state variable to an empty string, but all other code (including when you declare filtered in useState assumes that it is an array.
My guess is that you want to do:
if (activeFilter === "") {
setFiltered([]);
return;
}
Mistake was fixed I failed to see I need to update my setFiltered to an array instead of a empty string
if (activeFilter === "") {
setFiltered("");
return;
}
to
if (activeFilter === "") {
setFiltered([]);
return;
}
I'm trying to figure out on how to remove duplicates from an array of objects when entering multiple input skill tags. Maybe I am missing some key points here
const SkillsTags = ({ skillTags, setSkillTags, skill }) => {
const removeSkillTag = (i) => {
setSkillTags([...skillTags.filter((_, index) => index !== i)]);
};
const addSkillTag = (e) => {
e.preventDefault();
var updatedSkills = [...skillTags];
if (skill.current.value.trim().length !== 0) {
updatedSkills = [...skillTags, { SKILL_NAME: skill.current.value }];
}
setSkillTags(updatedSkills);
skill.current.value = "";
};
return (
<>
<label htmlFor="Skills">Skills:</label>
<div className="input-group">
<input
type="text"
placeholder="Enter Skills (Press Enter to add)"
onKeyPress={(e) => (e.key === "Enter" ? addSkillTag(e) : null)}
ref={skill}
/>
<button className="btn btn-outline-primary" onClick={addSkillTag}>
Add
</button>
</div>
<ul style={{ height: "12.5rem" }}>
{skillTags.map((val, index) => {
return (
<li key={index}>
{val.SKILL_NAME}
<button type="button" onClick={() => removeSkillTag(index)}>
Remove
</button>
</li>
);
})}
</ul>
</>
);
};
Demo here: https://codesandbox.io/s/add-skill-tags-nitthd?file=/src/SkillsTags.js
I think you are trying to filter duplicates, You can achieve this with simple Javascript.
const addSkillTag = (e) => {
e.preventDefault();
const isDuplicate = skillTags.some(function (item, idx) {
return item.SKILL_NAME === skill.current.value;
});
if (skill.current.value.trim().length !== 0) {
if (!isDuplicate) {
skillTags = [...skillTags, { SKILL_NAME: skill.current.value }];
}
}
setSkillTags(skillTags);
skill.current.value = "";
};
As said in title, my props is an empty object.
This is my component, in which i want to match a proper object in mapStateToProps.
The matching object exists, because when i pass and x.id === 1 , the object is being rendered.
const UserDetails = ({ episode, history }, props) => {
const { id } = useParams();
// const handleDelete = (id) => {
// if (window.confirm("Are you sure wanted to delete the episode ?")) {
// dispatch(deleteEpisode(id));
// }
// };
console.log("hej", props); // it prints empty object
console.log(episode);
return (
<div>
{episode ? (
<div>
<button onClick={() => history.push("/episodes")}>...back</button>
<h1> Tytuł: {episode.title}</h1>
<h3> Data wydania: {episode.release_date}</h3>
<h3> Series: {episode.series} </h3>
<img src={episode.img} alt="episode" />
{/* <div>
{episode.episode.characters.map((id) => {
const properActor = users.find((el) => el.id == id);
return <div>{`${properActor.name} `}</div>;
})}
</div> */}
<button onClick={() => history.push(`/episode/edit/${episode.id}`)}>
Edit
</button>
{/* <button onClick={() => handleDelete(episode.id)}>Delete</button> */}
<div>
<Link to={`/episodes/${episode.id}/addCharacter`}>
<button type="button">Dodaj postać do: {episode.title}</button>
</Link>
</div>
</div>
) : (
<div>Loading...</div>
)}
</div>
);
};
const mapStateToProps = (state, props) => {
return {
episode: state.episodes.episodes
? state.episodes.episodes.find((x) => x.id === props.match.params.id)
: null,
};
};
export default withRouter(connect(mapStateToProps, null)(UserDetails));
for anyone, who woudl like to see the whole project:
https://stackblitz.com/edit/node-fxxjko?file=db.json
hope it works,
to run the database u have to install npm json-server and run
EDIT:
If i do something like this:
const mapStateToProps = (state, props) => {
console.group("mapStateToProps");
console.log(props); // Does props.match.params.id exist? What is it?
console.log(state.episodes.episodes); // Is there an object in this array whose id matches the above?
console.groupEnd();
return {
episode: state.episodes.episodes
? state.episodes.episodes.find(
(x) => x.episodeId === props.match.params.episodeId
)
: null,
};
};
i see the this result:
https://imgur.com/a/ssrJjHV
You are not properly destructuring the rest of your props. Implement ellipsis to get the rest of the props back
It should be:
const UserDetails = ({ episode, history, ...props}) => {
//rest of your code
}
not:
const UserDetails = ({ episode, history }, props) => {
//rest of your code
}
Below is a basic example of the problem I'm experiencing.
I have a list of items that are in an array. The user can add another item to the list, and it shows on the page. Each item has a delete button and that delete button is a component inside the array item (this is so each item could have a button that does a different action.. in my example the action is deleting, but later it might be "Send" or "Delete" or "Cancel" or "Edit"...)
The trouble is, when I click the "Action" in this case delete, I want to know which item in the array this was. This way, I can get the array index and delete it. Or later grab additional details from the
import React, {useState} from "react"
function App() {
const [list, setList] = useState([])
function addRow(){
let newRow = {
name: "Test",
action: <span onClick={e=>removeThisRow()}>REMOVE</span>
}
setList([
...list,
newRow
])
}
function removeThisRow(){
// need this to remove the specific item from my list array...
console.log("removing...")
}
return (
<div>
{
list.map(item=>(
<div>
{item.name} | {item.action}
</div>
))
}
<div onClick={e=>addRow()}>ADD ROW</div>
</div>
);
}
export default App;
Working and tested
function App() {
const [list, setList] = React.useState([])
function addRow(){
let newRow = {
id: Math.random(),
name: `Test-${Math.random()}`,
action: (id) => <span onClick={()=>removeThisRow(id)}>REMOVE</span>
}
setList([
...list,
newRow
])
}
function removeThisRow(id){
setList(l => l.filter(li => li.id !== id))
}
return (
<div>
{
list.map((item)=>(
<div key={item.id}>
{item.name} | {item.action(item.id)}
</div>
))
}
<div onClick={e=>addRow()}>ADD ROW</div>
</div>
);
}
Id is just random number, I would use something more unique like uuid()
Here's an alternative approach to dynamically adding/editing/removing items from an array. Instead of unnecessarily creating JSX within an array, you can map over the array list and edit/remove the row based upon a unique id.
You can get even more sophisticated by adding two additional input toggles before adding a new row. These two toggles might add an isEditable and isRemovable properties to the row when when it's created. These properties can then be used to dynamically include or exclude buttons when the list is displayed. This is a cleaner approach as you're not recreating the same buttons over and over for each row, but flexible enough to conditionally render them.
On a related note, using the array index as a key is anti-pattern.
Demo Source Code:
Demo: https://q3wph.csb.app/
Code:
import * as React from "react";
import { v4 as uuid } from "uuid";
import Button from "./components/Button";
import Input from "./components/Input";
import Switch from "./components/Switch";
import "./styles.css";
export default function App() {
const [list, setList] = React.useState([]);
const [editRow, setEditRow] = React.useState({
id: "",
value: ""
});
const [newRowValue, setNewRowValue] = React.useState("");
const [newRowIsEditable, setNewRowEditable] = React.useState(true);
const [newRowIsRemovable, setNewRowRemovable] = React.useState(true);
const removeItem = (removeId) => {
setList((prevState) => prevState.filter(({ id }) => id !== removeId));
};
const editItem = (editId) => {
const { name } = list.find((item) => item.id === editId);
setEditRow({ id: editId, value: name });
};
const updateRowItem = ({ target: { value } }) => {
setEditRow((prevState) => ({ ...prevState, value }));
};
const handleRowUpdate = (e) => {
e.preventDefault();
const { id, value } = editRow;
if (!value) {
alert("Please fill out the row input before updating the row!");
return;
}
setList((prevState) =>
prevState.map((item) =>
item.id === id ? { ...item, name: value } : item
)
);
setEditRow({ id: "", value: "" });
};
const addNewRowItem = (e) => {
e.preventDefault();
if (!newRowValue) {
alert("Please fill out the new row item before submitting the form!");
return;
}
setList((prevState) => [
...prevState,
{
id: uuid(),
name: newRowValue,
isEditable: newRowIsEditable,
isRemovable: newRowIsRemovable
}
]);
setNewRowValue("");
setNewRowEditable(true);
setNewRowRemovable(true);
};
return (
<div className="app">
<h1>Dynamically Add/Edit/Remove Row</h1>
{list.length > 0 ? (
list.map(({ id, name, isEditable, isRemovable }) => (
<div className="uk-card uk-card-default uk-card-body" key={id}>
{editRow.id === id ? (
<form onSubmit={handleRowUpdate}>
<Input
placeholder="Add a new row..."
value={editRow.value}
handleChange={updateRowItem}
/>
<Button color="secondary" type="submit">
Update Row
</Button>
</form>
) : (
<>
<h2 className="uk-card-title">{name}</h2>
{isEditable && (
<Button
className="uk-margin-small-bottom"
color="primary"
type="button"
handleClick={() => editItem(id)}
>
Edit
</Button>
)}
{isRemovable && (
<Button
color="danger"
type="button"
handleClick={() => removeItem(id)}
>
Remove
</Button>
)}
</>
)}
</div>
))
) : (
<div>(Empty List)</div>
)}
<form
className="uk-card uk-card-default uk-card-body"
onSubmit={addNewRowItem}
>
<Input
placeholder="Add a new row..."
value={newRowValue}
handleChange={(e) => setNewRowValue(e.target.value)}
/>
<Switch
label="Editable"
handleChange={(e) => setNewRowEditable(Boolean(e.target.checked))}
name="Editable"
value={newRowIsEditable}
/>
<Switch
label="Removable"
handleChange={(e) => setNewRowRemovable(Boolean(e.target.checked))}
name="Removable"
value={newRowIsRemovable}
/>
<Button
className="uk-margin-small-bottom"
color="secondary"
type="submit"
>
Add Row
</Button>
<Button color="danger" type="button" handleClick={() => setList([])}>
Reset List
</Button>
</form>
</div>
);
}
I'm attaching an attribute to the span, named index and accessing that in removeThisRow.
import "./styles.css";
import React, { useState } from "react";
function App() {
const [list, setList] = useState([]);
function addRow() {
let newRow = {
name: "Test",
action: (
<span index={list.length} onClick={(e) => removeThisRow(e)}>
REMOVE
</span>
)
};
setList([...list, newRow]);
}
function removeThisRow(e) {
// need this to remove the specific item from my list array...
const index = e.target.getAttribute("index"); // got the index as a string
// Do whatever you want
}
return (
<div>
{list.map((item) => (
<div>
{item.name} | {item.action}
</div>
))}
<div onClick={(e) => addRow()}>ADD ROW</div>
</div>
);
}
export default App;
I am trying to delete an item in an array. However, my delete button is not executing my code and the array remains unchanged. I am not sure what to do.
My code is below:
//App.js
import React, { useState } from "react";
import Overview from "./components/Overview";
function App() {
const [task, setTask] = useState("");
const [tasks, setTasks] = useState([]);
function handleChange(e) {
setTask(e.target.value);
}
function onSubmitTask(e) {
e.preventDefault();
setTasks(tasks.concat(task));
setTask("");
}
//error happening here????---------------------------------------------------
function removeTask(itemId) {
setTasks(prevState => prevState.filter(({ id }) => id !== itemId));
}
return (
<div className="col-6 mx-auto mt-5">
<form onSubmit={onSubmitTask}>
<div className="form-group">
<label htmlFor="taskInput">Enter task</label>
<input
onChange={handleChange}
value={task}
type="text"
id="taskInput"
className="form-control"
/>
</div>
<div className="form-group">
<button type="submit" className="btn btn-primary">
Add Task
</button>
</div>
</form>
<Overview tasks={tasks} removeTask={removeTask} />
</div>
);
}
export default App;
Child Component:
import React from "react";
function Overview(props) {
const { tasks, removeTask } = props;
console.log(tasks)
return (
<>
{tasks.map((task, index) => {
return (
<>
<p key={index}>
#{index + 1} {task}
</p>
//this onClick isn't doing anything-------------------------------------
<button onClick={() => removeTask(index)}>Delete Task</button>
</>
);
})}
</>
);
}
export default Overview;
My 'tasks' state gives me an array, with items inside as strings. However, when I tried to filter the array, that didn't work. So instead of filtering by value, I tried to filter by id/index. Since the index would match it I thought that would remove the item from the array, even if there is just one item, it doesn't remove anything and the delete button just console logs the given array.
Any help would be greatly appreciated.
I think you need to pass the taskId instead of index here
<button onClick={() => removeTask(task.id /*index*/)}>Delete Task</button>
because removeTask function is dealing with taskId not with the index
However it's looks like you don't have id field on tasks even though you assuming it is there in setTasks(prevState => prevState.filter(({ id }) => id !== itemId));, so if you want to keep removing task by index, change removeTask as below.
function removeTask(index) { // remove by index
setTasks(prevState => {
const tasks = [...prevState]; // create new array based on current tasks
tasks.splice(index, 1); // remove task by index
return tasks; // return altered array
});
}
demo
Issue
Your delete method consumes an item id, but you pass it an index in the button's onClick handler.
Solution
Choose one or the other of id or index, and remain consistent.
Using id
function removeTask(itemId) {
setTasks(prevState => prevState.filter(({ id }) => id !== itemId));
}
...
<button onClick={() => removeTask(task.id)}>Delete Task</button>
Using index
function removeTask(itemIndex) {
setTasks(prevState => prevState.filter((_, index) => index !== itemIndex));
}
...
<button onClick={() => removeTask(index)}>Delete Task</button>
Since it doesn't appear your tasks are objects with an id property I suggest adding an id to your tasks. This will help you later when you successfully delete a task from you list since you'll also want to not use the array index as the react key since you expect to mutate your tasks array.
App.js
import { v4 as uuidV4 } from 'uuid';
...
function onSubmitTask(e) {
e.preventDefault();
setTasks(prevTasks => prevTasks.concat({
id: uuidV4(), // <-- generate new id
task
}));
setTask("");
}
function removeTask(itemId) {
setTasks(prevState => prevState.filter(({ id }) => id !== itemId));
}
Child
function Overview({ tasks, removeTask }) {
return (
{tasks.map(({ id, task }, index) => { // <-- destructure id & task
return (
<Fragment key={id}> // <-- react key on outer-most element
<p>
#{index + 1} {task}
</p>
<button onClick={() => removeTask(id)}>Delete Task</button>
</>
);
})}
);
}