i'm making a to do list with only javascript and css. When i add the task, it creates an article with a h1 and 2 icons. The check icon and delete icon. When i click the check icon, it add the class that changes the text style to line-through. The problem is that it's applying to all h1, and i want it to apply only to one specific h1.
function TaskName() {
window.taskName = {};
window.taskName.handdleClick = () => {
const $Task = document.querySelectorAll("h1");
$Task.forEach((e) => {
e.classList.toggle("task-check");
};
window.taskName.delete = () => {
ItemRemoval();
};
let $Input = document.getElementById("input-item");
let $task = $Input.value;
return /*html*/ `
<article id='task-article'>
<h1 id='task-name'>${$task}</h1>
<img src='images/check 1.png' alt='Completar tarefa' onclick='taskName.handdleClick()' id='check-icon'>
<img src='images/delete.svg' alt='Deletar tarefa' onclick='taskName.delete()' id='delete-icon'>
</article>
`;
}
Your problem starts at the structure of your code:
there's a function called TaskName that does a lot of things: creating HTML, deleting something from somewhere, handling a click event
you use the global namespace (window) to handle things
What do you need, if you want a Todo app?
a list of todos (probably an array)
a function to add a new Todo to the list of todos
a function to remove a Todo item from the list of todos
a function that sets 1 Todo item to done (OK, usually toggle between done and not done)
Here's a snippet that does this:
// list of todos & list manipulation functions
let todos = []
const addTodo = (newTodo, todos) => [...todos, newTodo]
const removeTodo = (idToRemove, todos) => todos.filter(({ id }) => idToRemove != id)
const toggleTodoDone = (idToToggle, todos) => todos.map(({ id, done, ...rest }) => id == idToToggle ? { id, done: !done, ...rest } : { id, done, ...rest })
const getTodoItem = (label) => ({
id: Date.now(),
done: false,
label,
})
// DOM manipulation & event handling
const input = document.getElementById("input-add-todo")
const btnAdd = document.getElementById("btn-add-todo")
const container = document.getElementById("container")
const resetInput = () => input.value = ''
btnAdd.addEventListener('click', function() {
const label = input.value
if (label) {
const newTodo = getTodoItem(label)
todos = addTodo(newTodo, todos)
updateContainer(container, todos)
resetInput()
}
})
const getTodoHtml = (todo) => {
const doneClass = todo.done ? ' done' : ''
return `
<div
class="todo-item${doneClass}"
data-id="${todo.id}"
>
${todo.label} - ${todo.done}
<button class="remove-todo" data-id="${todo.id}">X</button>
</div>
`
}
const getTodoListHtml = (todos) => todos.map(getTodoHtml).join('')
const registerEventHandlers = (container) => {
const els = document.querySelectorAll('.todo-item')
els.forEach(el => el.addEventListener('click', function() {
const id = el.dataset.id
todos = toggleTodoDone(id, todos)
updateContainer(container, todos)
}))
const btns = document.querySelectorAll('.remove-todo')
btns.forEach(btn => btn.addEventListener('click', function(e) {
e.stopPropagation()
const id = btn.dataset.id
todos = removeTodo(id, todos)
updateContainer(container, todos)
}))
}
const updateContainer = (container, todos) => {
container.innerHTML = getTodoListHtml(todos)
registerEventHandlers(container)
}
.todo-item {
cursor: pointer;
}
.todo-item.done {
text-decoration: line-through;
}
<div>
<input type="text" id="input-add-todo" />
<button id="btn-add-todo">ADD TODO</button>
</div>
<div id="container"></div>
Related
const element = (e) {
alert(e.haschildNodes())
}
<div className='results' onChange={element}>
<p>results</p>
</div>
the results div will have the p element based on the input of other element. myquestion is how can check if the results div has child elements or not 'the p tag'.
you can use MutationObserver:
const App = () => {
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
const observer = new MutationObserver(() => {
const len = ref.current.querySelectorAll('*');
console.log('results children length: ', len);
});
observer.observe(ref.current, { childList: true });
}
}, [ref])
return (<div className='results' ref={ref}>
<p>results</p>
</div>);
};
I have created two tabs that when clicked need to show a different set of products and a different set of filters for each selection. My problem is that when I click either tab and call setOptions within changeTab, I need to click each tab twice before it will update 'options', 'options' needs to contain each filter.
Obviously calling setOptions within the click handler is not correct but I can't figure out where or how to correctly update 'options'. Help greatly appreciated.
In the console logs below 'dedupedOptions' updates correctly on click
function filterProducts() {
const [categoryType, setCategory] = useState("Canine");
const [activeTabIndex, setActiveTabIndex] = useState(0);
const {
productData: {
products: { products }
}
} = useContext(AppContext);
const productsByCategory = products
.filter((product) => {
const { tags } = product;
return !!tags.find((tag) => tag.includes(categoryType));
})
.map((product) => ({
...product,
category: product.tags
.find((tag) => tag.includes("category:"))
.split(":")[1]
}));
let dedupedOptions = [];
productsByCategory.forEach((product) => {
const { tags } = product;
tags.forEach((tag) => {
const parts = tag.split(":");
const key = parts[0];
const value = parts[1] || null;
const validTag = tagKeysToDisplay.find(
(tagKeyToDisplay) => tagKeyToDisplay === key
);
if (
validTag &&
!dedupedOptions.find((dedupedOption) => dedupedOption.value === value)
) {
dedupedOptions = [
...dedupedOptions,
{
label: titleCase(value),
value,
selected: false
}
];
}
});
});
const [options, setOptions] = useState(dedupedOptions);
console.log(dedupedOptions);
console.log(options);
const changeTab = (index, category) => {
setCategory(category);
setActiveTabIndex(index);
setOptions(dedupedOptions);
};
const setFilter = useCallback(
(selectedOption) => {
const optionIsActive = options.find(
(option) => option.value === selectedOption.value
)?.selected;
let newOptions = [];
newOptions = [
...options.map((option) => {
if (option.value === selectedOption.value) {
return {
...option,
selected: !optionIsActive
};
}
return option;
})
];
setOptions(newOptions);
},
[options]
);
}
And the two elements set up as tabs to handle the click events. These are rendered within the same filterProducts function.
<div className="filter-products__tabs">
<div
className={`filter-products__tab
${activeTabIndex === 0 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 0, "Canine")}
>
<span>DOG</span>
</div>
<div
className={`filter-products__tab
${activeTabIndex === 1 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 1, "Feline")}
>
<span>CAT</span>
</div>
</div>
I reproduced your question by some changes in variable declarations in state.
be careful to declare variables in state and do the updates by listening the variable changes inside the useEffect.
here is the working code:\
https://codesandbox.io/s/quirky-http-e264i?file=/src/App.js
import "./styles.css";
import { useState, useContext, useCallback, useEffect } from "react";
export default function App() {
const [categoryType, setCategory] = useState("Canine");
const [activeTabIndex, setActiveTabIndex] = useState(0);
const [productsByCategory, setProductsByCategory] = useState([]);
const [dedupedOptions, setDedupedOptions] = useState([]);
const [options, setOptions] = useState(dedupedOptions);
const products = [
{ tags: ["category:Feline"], name: "one" },
{ tags: ["category:Canine"], name: "two" }
];
useEffect(() => {
const productsByCategory = products
.filter((product) => {
const { tags } = product;
return !!tags.find((tag) => tag.includes(categoryType));
})
.map((product) => ({
...product,
category: product.tags
.find((tag) => tag.includes("category:"))
.split(":")[1]
}));
setProductsByCategory(productsByCategory);
}, [categoryType]);
useEffect(() => {
let tmp_dedupedOptions = [];
const tagKeysToDisplay = ["category"];
productsByCategory.forEach((product) => {
const { tags } = product;
tags.forEach((tag) => {
const parts = tag.split(":");
const key = parts[0];
const value = parts[1] || null;
const validTag = tagKeysToDisplay.find(
(tagKeyToDisplay) => tagKeyToDisplay === key
);
if (
validTag &&
!tmp_dedupedOptions.find(
(dedupedOption) => dedupedOption.value === value
)
) {
tmp_dedupedOptions = [
...tmp_dedupedOptions,
{
label: value,
value,
selected: false
}
];
}
});
});
setDedupedOptions(tmp_dedupedOptions);
setOptions(tmp_dedupedOptions);
}, [productsByCategory]);
console.log("options: ", options);
const changeTab = (index, category) => {
setCategory(category);
setActiveTabIndex(index);
};
const setFilter = useCallback(
(selectedOption) => {
const optionIsActive = options.find(
(option) => option.value === selectedOption.value
)?.selected;
let newOptions = [];
newOptions = [
...options.map((option) => {
if (option.value === selectedOption.value) {
return {
...option,
selected: !optionIsActive
};
}
return option;
})
];
setOptions(newOptions);
},
[options]
);
// }
return (
<div>
<div className="filter-products__tabs">
<div
className={`filter-products__tab
${activeTabIndex === 0 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 0, "Canine")}
>
<span>DOG</span>
</div>
<div
className={`filter-products__tab
${activeTabIndex === 1 ? "is-active" : ""}`}
onClick={changeTab.bind(this, 1, "Feline")}
>
<span>CAT</span>
</div>
</div>
</div>
);
}
I have a react-table where I'm generating about 100 checkboxes dynamically. I have tried everything I can think of, but checking/unchecking the checkboxes takes a very long time before I can see any changes. It seems to be re-rendering every single checkbox before I can see a tick on the checkbox I just clicked on (the page freezes for about 5-10 secs). I'm using a change handler defined in a wrapper component passing as a prop.
// Generate columns for table
// this is added to the column props of React-Table
function setActionsColumns() {
return rolesActionsList.map((action, index) => {
const actionName = get(action, 'name');
return {
Header: actionName,
accessor: actionName,
Cell: row => {
const objectName = get(row, 'original.name');
let data = formData.permissions.filter(
perm => row.tdProps.rest.action === perm.action
);
let roleData = data[0];
let checked;
if (get(roleData, 'object') === row.tdProps.rest.object) {
checked = get(roleData, 'checked');
}
return (
<Checkbox
key={`${actionName}_${index}`}
name={actionName}
onChange={onCheckboxChange}
data-object={objectName}
checked={checked}
/>
);
},
};
});
}
// Checkbox handler defined in a class component
handleCheckboxChange = (event: SyntheticEvent<HTMLInputElement>) => {
const { roleData } = this.state;
const target = event.currentTarget;
const actionName = target.name;
const checked = target.checked;
const objectName = target.dataset.object.toUpperCase();
const checkedItem = {
action: actionName,
object: objectName,
checked,
};
let checkedItemsList = [];
let isInArray = roleData.permissions.some(perm => {
return perm.action && perm.object === checkedItem.object;
});
if (!isInArray) {
checkedItemsList = [...roleData.permissions, checkedItem];
} else {
checkedItemsList = roleData.permissions.filter(
perm => perm.action !== checkedItem.action
);
}
this.setState({
roleData: {
...this.state.roleData,
permissions: checkedItemsList,
},
});
};
I have a list of users with name, gender, age, etc from the following api: [https://gorest.co.in/public-api/users]. At the moment I can filter by gender === female, but doesn't work when I filter for gender === male.
Also, I try to filter the list of users by age. I get the date of birth and sorted afterwards, but it doesn't seem enough.
I have a LIVE EXAMPLE HERE
Here is the code:
import React from "react";
import axios from "axios";
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [],
search: ""
};
}
componentDidMount() {
this.getList();
}
/* get users list */
getList = async () => {
const api = 'https://gorest.co.in/public-api/users?_format=json&access-token=3qIi1MDfD-GXqOSwEHHLH73Y3UitdaFKyVm_';
await axios
.get(api)
.then(response => {
const list = response.data.result;
this.setState({
list,
isLoading: false
});
})
.catch(err => {
console.log(err);
});
};
/* handler for search bar */
handleChange = e => {
this.setState({
search: e.target.value
});
};
filterGender = gender => {
const lowerCaseGender = gender.toLowerCase();
const filteredGender = this.state.list.filter(
user => user.gender.toLowerCase().indexOf(lowerCaseGender) !== -1
);
this.setState({ list: filteredGender }, () => console.log(this.state.list));
};
filterAge = () => {
const ageList = this.state.list.map(age => {
return age.dob;
});
const filteredAge = this.state.list.filter(
e => e.dob.indexOf(ageList) !== -1
);
this.setState({ list: filteredAge }, () => console.log(this.state.list));
};
render() {
let style = {
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))",
padding: "1rem",
gridGap: "1rem 1rem"
};
return (
<div>
<input
placeholder="Search for a user..."
onChange={e => this.handleChange(e)}
/>
<button onClick={() => this.filterGender("male")}>Male</button>
<button onClick={() => this.filterGender("female")}>Female</button>
<button onClick={() => this.filterAge()}>Age</button>
<ul style={style}>
{this.state.list.map(user => {
return (
<li key={user.id}>
<div>
<img className="thumb" alt="" src={user._links.avatar.href} />
</div>
<div className="userInfo">
<p>
{user.first_name} {user.last_name}
</p>
</div>
</li>
);
})}
</ul>
</div>
);
}
}
Thank you!
The condition you are using,
user.gender.toLowerCase().indexOf(lowerCaseGender) !== -1
is matching male in female due to indexOf.
You should do this,
const filteredGender = this.state.list.filter(
user => user.gender.toLowerCase() === lowerCaseGender
);
There are two problems in your code.
Here I have updated your code.
https://codesandbox.io/s/epic-swartz-iu3qp
Problem 1: use of indexOf in filterGender function
Solution 1: directly compare the gender instead of indexOf
const filteredGender = this.list.filter(
user => user.gender.toLowerCase() === lowerCaseGender
);
Problem 2: You are filtering records from the this.state.list variable and then update it in same state variable.
Solution 2: Use another variable this.list in constructor and use it to filter the records.
Define variable in constructor
constructor(props) {
super(props);
this.state = {
list: [],
search: ""
};
this.list = []; // define new list here...
}
You have to assign upcoming list to this.list variable in getList function
this.list = response.data.result;
getList function should be look like below.
getList = async () => {
const api =
"https://gorest.co.in/public-api/users?_format=json&access-token=3qIi1MDfD-GXqOSwEHHLH73Y3UitdaFKyVm_";
await axios
.get(api)
.then(response => {
this.list = response.data.result; // assign list in variable
this.setState({
list:this.list,
isLoading: false
});
})
.catch(err => {
console.log(err);
});
};
Now you have to update both filter function as below. Instead of this.state.list use this.list.
filterGender = gender => {
const lowerCaseGender = gender.toLowerCase();
const filteredGender = this.list.filter(
user => user.gender.toLowerCase() === lowerCaseGender
);
this.setState({ list: filteredGender }, () => console.log(this.state.list));
};
filterAge = () => {
const ageList = this.list.map(age => {
return age.dob;
});
const filteredAge = this.list.filter(
e => e.dob.indexOf(ageList) !== -1
);
this.setState({ list: filteredAge }, () => console.log(this.state.list));
};
Hope this will work for you!
Full working demo with corrected code in same environment
https://codesandbox.io/s/brave-carson-6zilx
Gender
Issue - indexOf function use does't achieves anything.
Resolution -:
Using simple Array.filter with gender equality.
This function needs to be changed for gender filter -:
filterGender = gender => {
const filteredGender = this.state.list.filter(
user => user.gender.toLowerCase() === gender
);
this.setState({ list: filteredGender }, () => console.log(this.state.list));
};
Age
Issue - Filtering won't be helpful to sort data.
Resolution -:
Using Array.sort and adding a comparator function.
This function needs to be changed for age sorting -:
filterAge = () => {
const filteredAge = this.state.list.sort(function(a, b) {
return new Date(b.dob) - new Date(a.dob);
});
this.setState({ list: filteredAge }, () => console.log(this.state.list));
};
I am have trouble figuring out how to get this code to work. I am trying to remove a todo item based on which one was clicked. My goal eventually is to delete based on which button was clicked.
Here is my code so far:
import uuidv4 from 'uuid/v4'
let text;
let todos = [];
document.querySelector('#new-todo').addEventListener('keypress', e =>
{
text = e.target.value;
if (e.keyCode === 13 || e.which === 13) {
e.preventDefault();
addTodo(text);
renderTodo();
console.log(todos);
}
});
const addTodo = text =>
{
todos.push(
{
id: uuidv4(),
text
});
}
const renderTodo = () =>
{
let node = document.createElement("p");
let textnode = document.createTextNode(text);
node.appendChild(textnode);
document.getElementById("todos").appendChild(node);
}
document.querySelector('#todos').addEventListener('click', () =>
{
removeTodo(todo.id);
console.log(todos);
});
const removeTodo = id =>
{
const todoIndex = todos.findIndex((todo) => todo.id === id)
if (todoIndex > -1)
{
todos.splice(todoIndex, 1);
}
};