Checkbox for select all in react js - javascript

I wanted to create a select-all checkbox, each product row has a checkbox, once the checkbox is clicked it will take its product id, variations, and count to calculate and display the total price of the product selected. I wanted to do like when clicking on select all, all the other checks need to tick and display the total of all product prices, I did the code for each row checkbox and it work but I am not sure how to do for select all checkbox, need help on it, below are code, and https://codesandbox.io/s/select-all-checkbox-uzmllg this is codesand box.
import "./styles.css";
import React, { useEffect, useState } from "react";
export default function App() {
const [isCheckAll, setIsCheckAll] = useState(false);
const [checkedItems, setCheckedItems] = useState(
JSON.parse(localStorage.getItem("checkedItems") || "[]")
);
useEffect(() => {
localStorage.setItem("checkedItems", JSON.stringify(checkedItems));
}, [checkedItems]);
const addChecked = (itemId, variationId, qty) => {
setCheckedItems([
...checkedItems,
{ ProductID: itemId, VariationID: variationId, Count: qty }
]);
};
const removeChecked = (itemId, variationId) => {
const toBeRemove = checkedItems.find(
(item) => item.ProductID === itemId && item.VariationID === variationId
);
if (toBeRemove) {
checkedItems.splice(checkedItems.indexOf(toBeRemove), 1);
setCheckedItems([...checkedItems]);
}
};
const getCheckedStatus = (itemId, variationId) => {
const found = checkedItems.find(
(item) => item.ProductID === itemId && item.VariationID === variationId
);
return found !== null;
};
const handleSelectAll = (e) => {
if (isCheckAll) {
//
}
};
return (
<div className="App">
{cartData.Result.map((shop) =>
shop.ShopCartList.map((cart) => (
<div key={cart.ShopID} md="12" lg="12">
{cart.productCartList.map((items) => {
return (
<div key={items.VariationID} md="12" lg="12">
<div id="additem" className="pt-5">
{items.Stock === 0 ? (
<h6 className="bg-light text-danger font-weight-bold ">
SOLD OUT
</h6>
) : (
<input
type="checkbox"
value={getCheckedStatus(
items.ProductID,
items.VariationID
)}
onChange={(e) => {
if (e.target.checked) {
addChecked(
items.ProductID,
items.VariationID,
items.Count
);
} else {
removeChecked(
items.ProductID,
items.VariationID,
items.Count
);
}
}}
/>
)}
</div>
</div>
);
})}
</div>
))
)}
<div>
<input
type="checkbox"
name="selectAll"
id="selectAll"
handleClick={handleSelectAll}
isChecked={isCheckAll}
/>
Select All
</div>
</div>
);
}

In handleSelectAll you need to set checkedItems is all your array items. You dont need isCheckAll state, you can see check all status by verify length of your checkedItems
const flattenCartData = (cartData) => {
const arr = [];
cartData.Result.forEach((shop) => {
shop.ShopCartList.forEach((cart) => {
cart.productCartList.forEach((items) => {
arr.push(items);
});
});
});
return arr;
};
export default function App() {
const [checkedItems, setCheckedItems] = useState(
JSON.parse(localStorage.getItem("checkedItems") || "[]")
);
const ITEMS = flattenCartData(cartData);
const isCheckAll = checkedItems.length === ITEMS.length;
useEffect(() => {
localStorage.setItem("checkedItems", JSON.stringify(checkedItems));
}, [checkedItems]);
const addChecked = (itemId, variationId, qty) => {
setCheckedItems([
...checkedItems,
{ ProductID: itemId, VariationID: variationId, Count: qty }
]);
};
const removeChecked = (itemId, variationId) => {
const toBeRemove = checkedItems.find(
(item) => item.ProductID === itemId && item.VariationID === variationId
);
if (toBeRemove) {
checkedItems.splice(checkedItems.indexOf(toBeRemove), 1);
setCheckedItems([...checkedItems]);
}
};
const getCheckedStatus = (itemId, variationId) => {
const found = checkedItems.find(
(item) => item.ProductID === itemId && item.VariationID === variationId
);
return found !== undefined;
};
const handleSelectAll = (e) => {
if (isCheckAll) {
setCheckedItems([]);
} else setCheckedItems([...ITEMS]);
};
return (
<div className="App">
{cartData.Result.map((shop) =>
shop.ShopCartList.map((cart) => (
<div key={cart.ShopID} md="12" lg="12">
{cart.productCartList.map((items) => {
return (
<div key={items.VariationID} md="12" lg="12">
<div id="additem" className="pt-5">
{items.Stock === 0 ? (
<h6 className="bg-light text-danger font-weight-bold ">
SOLD OUT
</h6>
) : (
<div>
<input
type="checkbox"
checked={getCheckedStatus(
items.ProductID,
items.VariationID
)}
onChange={(e) => {
if (e.target.checked) {
addChecked(
items.ProductID,
items.VariationID,
items.Count
);
} else {
removeChecked(
items.ProductID,
items.VariationID,
items.Count
);
}
}}
/>
<span>{items.ProductName}</span>
</div>
)}
</div>
</div>
);
})}
</div>
))
)}
<div>
<input
type="checkbox"
name="selectAll"
id="selectAll"
onChange={handleSelectAll}
checked={isCheckAll}
/>
Select All
</div>
</div>
);
}
I have created a codesandbox. You can check, hope it help!

Related

How to return jsx for multiple arrays iterated?

I have the following arrays iterated and I'm able to console.log the result I want.
import React from 'react';
const MyApplications = ({ currentUser, jobs, jobApplications }) => {
const jobAppsFromCurrentUser = jobApplications.filter(jobApp => jobApp.musician_id === currentUser.id)
return (
<div>
<div>
{
jobs.filter(job => {
jobAppsFromCurrentUser.map(jobApp => {
if (jobApp.job_id === job.id) {
console.log(job)
}
})
})
}
</div>
</div>
)
}
export default MyApplications
Result:
But ultimately, I need to render each job as jsx. I want to be able to do something like this (this doesn't return anything):
<div>
{
jobs.filter(job => {
jobAppsFromCurrentUser.map(jobApp => {
if (jobApp.job_id === job.id) {
return (
<div>
<h1>{job.title}</h1>
</div>
)
}
})
})
}
</div>
Solution:
import React from 'react';
const MyApplications = ({ currentUser, jobs, jobApplications }) => {
const jobAppsFromCurrentUser = jobApplications.filter(jobApp => jobApp.musician_id === currentUser.id)
const includesID = (id) => {
const onFilter = jobAppsFromCurrentUser.filter((jobApp) => jobApp.job_id == id);
return onFilter.length > 0 ? true : false;
};
return (
<div>
<div>
{jobs.map((job) => {
if (includesID(job.id)) {
return (
<div key={job.id}>
<h1>{job.title}</h1>
</div>
);
}
})}
</div>
</div>
)
}
export default MyApplications
First check if the ID's match.
const includesID = (id) => {
const onFilter = jobs.filter((item) => item.id == id);
return onFilter.length > 0 ? true : false;
};
And render it as
<div>
{jobAppsFromCurrentUser.map((jobApp) => {
if (includesID(jobApp.id)) {
return (
<div>
<h1>{job.title}</h1>
</div>
);
}
})}
</div>

Feedback on current iteration of React code

I am looking for feedback regarding this React code I am writing. I previously posted when I first started out on this and I have since iterated on it and tried to implement best practices. I am learning a lot and I cannot help, but feel that my current code is clunky. In what ways can I improve the design?
I am having a hard time figuring out what needs to be a component, what needs to be a function, and what can just be a value (even then whether it should be state). I also feel like I am misusing variable scope, but I can't put my finger on how. One specific thing I cannot resolve is ESlint is highlighting mostly everything in my file with this error "Function component is not a function declaration". Any and all feedback is appreciated. Thank you!
/* eslint-disable react/prop-types */
/* eslint-disable no-underscore-dangle */
import axios from 'axios';
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { GetWishes, InsertWish } from './apis';
// Main component, acts a wrapper for the entire screen content
const Wishlist = () => {
const [loading, setLoading] = useState('initial');
const [listOfWishes, setListOfWishes] = useState('default');
// Passed down to update the main list state
function updateListOfWishes(list) {
setListOfWishes([...list]);
}
// Sorting lists dynamically
function dynamicSort(property) {
let sortOrder = 1;
let newprop = '';
if (property[0] === '-') {
sortOrder = -1;
newprop = property.substr(1);
}
return (a, b) => {
let result = 0;
if ((a[newprop] < b[newprop])) {
result = -1;
} else if (a[newprop] > b[newprop]) {
result = 1;
}
return result * sortOrder;
};
}
// Get all items from DB, this is main list
async function GetWishesList() {
try {
const apiresp = await GetWishes();
apiresp.sort(dynamicSort('-source'));
const goodlist = apiresp.map((item) => ({ ...item, isReadOnly: true, show: true }));
setListOfWishes(goodlist);
setLoading('false');
} catch (e) {
console.log(`Error in Wishlist.GetWishesList: ${e.message}`);
}
}
// Only once, get items and set loading state
useEffect(() => {
setLoading('true');
GetWishesList();
}, []);
if (loading === 'initial') {
return <h2 className="content">Initializing...</h2>;
}
if (loading === 'true') {
return <h2 className="content">Loading...</h2>;
}
// Return header and content, pass down function for deep state update
return (
<div className="contentwrapper">
<WishlistHeader fullList={listOfWishes} updateListOfWishes={updateListOfWishes} />
<WishTable fullList={listOfWishes} updateListOfWishes={updateListOfWishes} />
</div>
);
}
// Header component
const WishlistHeader = (props) => {
const [filter, setFilter] = useState('all');
let list = props.fullList;
// Get length of current filtered list
function getShowCount(list) {
return list.filter((item) => item.show === true).length;
}
// Update shown list items when filter changes
const HandleFilterChange = (e) => {
const { fullList, updateListOfWishes } = props;
const { value } = e.target;
for (let i = fullList.length - 1; i >= 0; i -= 1) {
fullList[i].isReadOnly = true;
fullList[i].show = true;
if (value !== 'all' && fullList[i].category !== value) {
fullList[i].show = false;
}
}
setFilter(value);
const newlist = fullList.map(i => {
return { ...i };
});
updateListOfWishes(newlist);
}
// Return header component content
return (
<div className="contentBanner">
<h1 className="wishTitle">
Wishes:
{' '}
{getShowCount(list)}
</h1>
<label htmlFor="category">
<p className="bannerFilter">Category</p>
<select id="category" name="category" value={filter} onChange={(e) => HandleFilterChange(e)}>
<option value="all">All</option>
<option value="default">Default</option>
<option value="camping">Camping</option>
<option value="hendrix">Hendrix</option>
<option value="decor">Decor</option>
</select>
</label>
</div>
);
}
// Component to show list of items
function WishTable(props) {
const rows = [];
let { fullList, updateListOfWishes } = props;
if (fullList === null) {
console.log('currentList is null');
} else {
fullList.forEach(function (item) {
rows.push(
<div key={item._id} >
<WishRow item={item} currentList={fullList} updateListOfWishes={updateListOfWishes} />
</div>)
})
}
return (
<div className="content">
{rows}
</div>
);
};
// Individual row render for each item
const WishRow = (props) => {
let item = props.item;
let prevItem = useRef(item);
// Store unedited item in case of cancel, mark not read only
const handleEdit = () => {
let { item, currentList, updateListOfWishes } = props;
prevItem.current = { ...item };
const newlist = currentList.map(i => {
if (i._id === item._id) {
return { ...i, isReadOnly: false }
}
return { ...i };
});
updateListOfWishes(newlist);
};
// Send item to DB
async function insertWish(item) {
try {
await InsertWish(item);
} catch (e) {
console.log(`Error in wishlist.insertWish: ${e.message}`);
}
};
// Send current item info to DB and mark read only
const handleSubmit = () => {
let { item, currentList, updateListOfWishes } = props;
insertWish(item);
const newlist = currentList.map(i => {
if (i._id === item._id) {
return { ...i, isReadOnly: true };
}
return { ...i };
});
updateListOfWishes(newlist);
};
// Return content for submit button
function Submit(item) {
if (!item.isReadOnly) {
return (
<span>
<button className="typicalbutton" type="button" onClick={() => handleSubmit(item)}>
Submit
</button>
</span>
);
}
return null;
};
// Return content for edit button
function ShowEdit(item) {
if (item.source === 'manual') {
return (
<span >
<button className="typicalbutton righthand" type="button" onClick={() => handleEdit(item, props.currentList)}>
Edit
</button>
</span>
);
}
return null;
};
// Revert to unedited item and mark read only
const handleCancel = () => {
let { item, currentList, updateListOfWishes } = props;
const newlist = currentList.map(i => {
if (i._id === item._id) {
return { ...prevItem.current, isReadOnly: true }
}
return { ...i };
});
updateListOfWishes(newlist);
};
// Return content for cancel button
function Cancel(item) {
if (!item.isReadOnly) {
return (
<span>
<button className="typicalbutton" type="button" onClick={(e) => handleCancel(e, prevItem, item)}>
Cancel
</button>
</span>
);
}
return null;
};
// Update item when fields edited
const handleChange = (e) => {
let { item, currentList, updateListOfWishes } = props;
const { name, value } = e.target;
item[name] = value;
const newlist = currentList.map(i => {
return { ...i };
});
updateListOfWishes(newlist);
};
// Update item for category change
const handleCategoryChange = (e) => {
const { value } = e.target;
let { item, currentList, updateListOfWishes } = props;
item.category = value;
const newlist = currentList.map(i => {
return { ...i };
});
updateListOfWishes(newlist);
};
// Open url in new tab
const openInTab = (url) => {
const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
if (newWindow) newWindow.opener = null;
};
// Get the url from backend
async function getUrl(link) {
const toflask = `/go_outside_flask/${link}`;
const tempurl = await axios.get(toflask);
const newurl = tempurl.data;
return newurl;
}
// Get the url from backend and go to it in new tab
const goToLink = async (link) => {
let taburl = '';
try {
taburl = await getUrl(link);
return openInTab(taburl);
} catch (e) {
console.log(`Error getting url from link: ${link} ${e.message}`);
return window.location.href;
}
};
// Row content, if read only show just fields, if not read only then show different buttons and editable fields
return (
<div >
{item.show ? (
<div className="wish">
<div className="wishatt">
{
item.isReadOnly ? (
<div>
<span className="wishatt capital">
Category:
{item.category}
</span>
{ShowEdit(item)}
<div className="wishatt">
Item Name:
{item.name}
</div>
<div className="wishatt">
Description:
{item.description}
</div>
<div className="wishatt">
Cost:
{item.cost}
</div>
<span>Link: </span>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a className="wishatt" href="#" onClick={(e) => goToLink(item.link)}>{item.link}</a>
<div className="wishatt">
Quantity:
{item.quantity}
</div>
</div>
)
: (
<span>
<label htmlFor="category">
Category:
<select name="category" onChange={(e) => handleCategoryChange(e, item)} value={item.category}>
<option value="default">Default</option>
<option value="camping">Camping</option>
<option value="hendrix">Hendrix</option>
<option value="decor">Decor</option>
</select>
</label>
<span className="righthandSection">
{Submit(item)}
{Cancel(item)}
</span>
<div>
<div>
<label htmlFor="name">
Item Name:
</label>
<input className="wishatt" name="name" placeholder="Name" onChange={(e) => handleChange(e, item)} value={item.name} />
</div>
<div>
<label htmlFor="description">
Description:
</label>
<input className="wishatt" name="description" placeholder="Description" onChange={(e) => handleChange(e, item)} value={item.description} />
</div>
<div>
<label htmlFor="cost">
Cost:
</label>
<input className="wishatt" name="cost" placeholder="Cost" onChange={(e) => handleChange(e, item)} value={item.cost} />
</div>
<div>
<label htmlFor="link">
Link:
</label>
<input className="wishatt" name="link" placeholder="Link" onChange={(e) => handleChange(e, item)} value={item.link} />
</div>
<div>
<label htmlFor="quantity">
Quantity:
</label>
<input className="wishatt" name="quantity" placeholder="Quantity" onChange={(e) => handleChange(e, item)} value={item.quantity} />
</div>
<div className="wishatt">
Wishlist:
{item.wishlist}
</div>
</div>
</span>
)
}
</div>
</div>
) : null}
</div>
)
}
export default Wishlist;

While creating item from parent to child component, going to infinite loop in React

This is my home page and getting list data then creating List component as much as need.
Then going to List component and creating list item. so creating new component using map function. I tried different ways for it but I can't figure out. It goes infinite loop whatever I did.
Home.jsx
const [lists, setLists] = useState([]);
useEffect(() => {
const getRandomList = async () => {
try {
const res = await axios.get(`/lists${type ? "?type=" + type : ""}`, {
headers: {
"Content-Type": "application/json;charset=UTF-8",
token: token,
},
});
setLists(res.data);
} catch (error) {
console.log(error);
}
};
getRandomList();
}, [type]);
return (
<div className="home">
<Navbar />
<Featured type={type} />
{lists.map((list, index) => {
return <List key={index} list={list}/>;
})}
</div>
);
}
in List component, creating List item component as much as need.
List.jsx
export default function List({ list }) {
const [isMoved, setIsMoved] = useState(false);
const [slideNumber, setSlideNumber] = useState(0);
const [clickLimit, setClickLimit] = useState(window.innerWidth / 230);
const [lists, setLists] = useState([])
const listRef = useRef();
useEffect( () =>{
const getList = ()=>{
setLists(list.content)
}
getList()
},[list])
const handleClick = (direction) => {
setIsMoved(true);
let distance = listRef.current.getBoundingClientRect().x - 50;
if (direction === "left" && slideNumber > 0) {
setSlideNumber(slideNumber - 1);
listRef.current.style.transform = `translateX(${230 + distance}px)`;
}
if (direction === "right" && slideNumber < 10 - clickLimit) {
setSlideNumber(slideNumber + 1);
listRef.current.style.transform = `translateX(${-230 + distance}px)`;
}
};
return (
<div className="list">
<span className="listTitle">Continue to watch</span>
<div className="listWrapper">
<ArrowBackIosOutlined
className="sliderArrow left"
onClick={() => handleClick("left")}
style={{ display: !isMoved && "none" }}
/>
<div className="listContainer" ref={listRef}>
{lists.map((item, index) => {
return <ListItem key={index} index={index} item={item} />;
})}
</div>
<ArrowForwardIosOutlined
className="sliderArrow right"
onClick={() => handleClick("right")}
/>
</div>
</div>
);
}
last component here.
ListItem.jsx
export default function ListItem({ index, item }) {
const [isHovered, setIsHovered] = useState(false);
const [movie, setMovie] = useState({});
useEffect(() => {
const getMovie = async (item) => {
try {
const res = await axios.get("/movies/find/" + item, {
headers: {
"Content-Type": "application/json;charset=UTF-8",
token: token,
},
});
setMovie(res.data);
} catch (error) {
console.log(error);
}
};
getMovie()
}, [item]);
return (
<div
className="listItem"
style={{
left:
isHovered &&
Object.values(index) * 225 - 50 + Object.values(index) * 2.5,
}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<img src={movie.img} alt="" />
{isHovered && (
<>
<video src={movie.trailer} autoPlay loop></video>
<div className="itemInfo">
<div className="itemIcons">
<PlayArrow className="itemIcon" />
<Add className="itemIcon" />
<ThumbUpAltOutlined className="itemIcon" />
<ThumbDownAltOutlined className="itemIcon" />
</div>
<div className="itemInfoTop">
<span>1 hour 14 mins</span>
<span className="limit">{movie.limit}</span>
<span>{movie.year}</span>
</div>
<div className="itemDescription">{movie.description}</div>
<div className="itemGenre">{movie.genre}</div>
</div>
</>
)}
</div>
);
}
Try to remove the useEffect from the List component and map on the list.content directly:
export default function List({ list }) {
const [isMoved, setIsMoved] = useState(false);
const [slideNumber, setSlideNumber] = useState(0);
const [clickLimit, setClickLimit] = useState(window.innerWidth / 230);
const listRef = useRef();
const handleClick = (direction) => {
setIsMoved(true);
let distance = listRef.current.getBoundingClientRect().x - 50;
if (direction === "left" && slideNumber > 0) {
setSlideNumber(slideNumber - 1);
listRef.current.style.transform = `translateX(${230 + distance}px)`;
}
if (direction === "right" && slideNumber < 10 - clickLimit) {
setSlideNumber(slideNumber + 1);
listRef.current.style.transform = `translateX(${-230 + distance}px)`;
}
};
return (
<div className="list">
<span className="listTitle">Continue to watch</span>
<div className="listWrapper">
<ArrowBackIosOutlined
className="sliderArrow left"
onClick={() => handleClick("left")}
style={{ display: !isMoved && "none" }}
/>
<div className="listContainer" ref={listRef}>
{list.content.map((item, index) => {
return <ListItem key={index} index={index} item={item} />;
})}
</div>
<ArrowForwardIosOutlined
className="sliderArrow right"
onClick={() => handleClick("right")}
/>
</div>
</div>
);
}

Checkbox value not being saved in LocalStorage and after clicking edit item the add button is not coming again

I was trying to make a ToDo Site and I wanted to save if the checkbox is checked or not in LocalStorage. But whenever I am checking the button the values inside the last are vanishing and it is not even being saved in the LocalStorage.
The following is my code:
import React from "react";
import Navbar from './components/Navbar'
import '../src/App.css'
export default function TodoInput() {
const saveLocalTasks = () => {
let savedTasks = localStorage.getItem('tasks');
console.log(savedTasks)
if (savedTasks) {
return JSON.parse(localStorage.getItem('tasks'));
} else {
return [];
}
}
const [task, setTask] = React.useState('')
// const [count, setCount] = React.useState(0)
const [taskList, setTaskList] = React.useState(saveLocalTasks)
const [disable, setDisable] = React.useState(true)
const [edit, setEdit] = React.useState(true)
const [isTaskEdit, setIsTaskEdit] = React.useState(null)
React.useEffect(() => {
localStorage.setItem('tasks', JSON.stringify(taskList))
}, [taskList]);
const updateTaskList = () => {
if (task && !edit) {
setTask('')
setTaskList(
taskList.map((item) => {
if(item.key === isTaskEdit) {
return {...item, object: task}
}
setEdit(true)
return item
}
)
)
}else {
setTaskList([...taskList, {object: task, key: Date.now(), completed: false}])
setTask('')
// setCount(count + 1)
setDisable(true)
}
}
const inputValue = e => {
setTask(e.target.value)
e.target.value === '' || task === '' || task.length === 0
?
setDisable(true)
:
setDisable(false)
}
console.log(task.length)
const deleteTaskListItem = (key) => {
const updatedList = taskList.filter((item) => {
return (
item.key !== key
)
})
setTaskList(updatedList)
// setCount(count - 1)
}
const editTask = (key) => {
let newTask = taskList.find((item) => {
return (
item.key === key
)
})
console.log(newTask)
setEdit(false)
setTask(newTask.object)
setIsTaskEdit(key)
}
const boxChecked = (key) => {
let checkedTask = taskList.map((item) => {
if (item.key === key) {
taskList.completed = !taskList.completed
}
return (
task
)
})
setTaskList(checkedTask)
}
return (
<div>
<Navbar />
<header>
<div className="todolist-border">
<div className="todo-input-form">
<input
className = "inputText"
placeholder="Add a Task"
value={task}
onChange = {inputValue}
/>
{
edit
?
<button
disabled = {disable}
onClick = {updateTaskList} className="todo-add-button">
+
</button>
:
<button className="edit-button" onClick={updateTaskList}>
<i className="fas fa-edit"></i>
</button>
}
</div>
<div>
{taskList.map((item) => {
return (
<div key = {item.key} className="todolist-div">
<input type="checkbox" className="list-checkbox" id="completed"
onChange={() => boxChecked(item.key)}
checked = {item.completed}
key = {item.key}
>
</input>
<p>{item.object}</p>
<div>
<button className="edit-button" onClick={() => editTask(item.key)}>
<i className="fas fa-edit"></i>
</button>
<button onClick={()=>deleteTaskListItem(item.key)} className="delete-button">
X
</button>
</div>
</div>
)
})}
</div>
</div>
</header>
</div>
)
}
The following error occurs when I check the checkbox:
And also I whenever I click the edit button, option to edit comes on the imput box and the add button changes to edit button. I put the state setEdit(True) after the edit button click but the following error shows up whenever I click on the edit button:
Kindly check and please correct the errors in the code
The link to the site is:
https://to-do-ist.netlify.app/
The site does not have the above code, but has the bugs I mentioned(edit button and checkbox)
The code for the site is as follows:
import React from "react";
import Navbar from './components/Navbar'
import '../src/App.css'
export default function TodoInput() {
const saveLocalTasks = () => {
let savedTasks = localStorage.getItem('tasks');
console.log(savedTasks)
if (savedTasks) {
return JSON.parse(localStorage.getItem('tasks'));
} else {
return [];
}
}
const [task, setTask] = React.useState('')
const [count, setCount] = React.useState(0)
const [taskList, setTaskList] = React.useState(saveLocalTasks)
const [disable, setDisable] = React.useState(true)
const [edit, setEdit] = React.useState(true)
const [isTaskEdit, setIsTaskEdit] = React.useState(null)
React.useEffect(() => {
localStorage.setItem('tasks', JSON.stringify(taskList))
}, [taskList]);
const updateTaskList = () => {
if (task && !edit) {
setTaskList(
taskList.map((item) => {
if(item.key === isTaskEdit) {
return {...item, object: task}
}
return item
})
)
}else {
setTaskList([...taskList, {object: task, key: Date.now()}])
setTask('')
setCount(count + 1)
setDisable(true)
}
// setEdit(true)
}
const inputValue = e => {
setTask(e.target.value)
e.target.value === '' || task === '' || task.length === 0
?
setDisable(true)
:
setDisable(false)
}
console.log(task.length)
const deleteTaskListItem = (key) => {
const updatedList = taskList.filter((item) => {
return (
item.key !== key
)
})
setTaskList(updatedList)
setCount(count - 1)
}
const editTask = (key) => {
let newTask = taskList.find((item) => {
return (
item.key === key
)
})
console.log(newTask)
setEdit(false)
setTask(newTask.object)
setIsTaskEdit(key)
}
return (
<div>
<Navbar />
<header>
<div className="todolist-border">
<div className="todo-input-form">
<input
className = "inputText"
placeholder="Add a Task"
value={task}
onChange = {inputValue}
/>
{
edit
?
<button
disabled = {disable}
onClick = {updateTaskList} className="todo-add-button">
+
</button>
:
<button className="edit-button" onClick={updateTaskList}>
<i className="fas fa-edit"></i>
</button>
}
</div>
<div>
{taskList.map((item) => {
return (
<div key = {item.key} className="todolist-div">
<input type="checkbox" className="list-checkbox">
</input>
<p>{item.object}</p>
<div>
<button className="edit-button" onClick={() => editTask(item.key)}>
<i className="fas fa-edit"></i>
</button>
<button onClick={()=>deleteTaskListItem(item.key)} className="delete-button">
X
</button>
</div>
</div>
)
})}
</div>
</div>
</header>
</div>
)
}

Best way to implement dynamic array of inputs in React

Currently I have implemented it using plain javascript array. But there seems to be few problems.
onChange, onDelete has to iterate all over the array which could be better like O(1)
When some item gets deleted key property of other items get changed. I'm not sure if it's bad or not.
Using immutable.js can get rid of problem 1. Is number 2 really a problem? Is there a better alternative than immutable.js?
Maybe I'm not asking the right questions, what's the best way?
import React, { useState } from "react";
export default function() {
const [rows, setRows] = useState([""]);
const onChange = (e, i) => {
setRows(rows.map((row, index) => (index !== i ? row : e.target.value)));
};
const onDelete = i => {
setRows(rows.filter((_, index) => i !== index));
};
return (
<>
{rows.map((row, index) => {
return (
<div key={index}>
<input value={row} onChange={e => onChange(e, index)} />
{index !== 0 && (
<button onClick={() => onDelete(index)}>
- delete row
</button>
)}
</div>
);
})}
<button onClick={() => setRows(rows.concat([""]))}>
+ add row
</button>
</>
);
}
UPDATE
I tried using immutable-js OrderedMap. Now key property of elements won't change and onChange and onDelete won't iterate over everything. Is this better than before?
import React, { useState } from "react";
import { OrderedMap } from "immutable";
export default function() {
const [inputState, setInputState] = useState({
rows: OrderedMap(),
nextKey: Number.MIN_VALUE,
});
function onChange(k, v) {
setInputState({
...inputState,
rows: inputState.rows.update(k, () => v),
});
}
function addRow() {
const { rows, nextKey } = inputState;
setInputState({
rows: rows.set(nextKey, ""),
nextKey: nextKey + 1,
});
}
function deleteItem(k) {
setInputState({
...inputState,
rows: inputState.rows.delete(k),
});
}
return (
<>
{inputState.rows.entrySeq().map(([k, v]) => {
return (
<div key={k}>
<input
value={v}
onChange={({ target: { value } }) => {
onChange(k, value);
}}
/>
<button onClick={() => deleteItem(k)}>-</button>
</div>
);
})}
<button onClick={addRow}>+ add row</button>
</>
);
}
UPDATE: 2
Also tried using plain javascript Map.
import React, { useState } from "react";
export default function() {
const [inputState, setInputState] = useState({
rows: new Map(),
nextKey: Number.MIN_VALUE,
});
function onChange(k, v) {
const { rows, nextKey } = inputState;
rows.set(k, v);
setInputState({
nextKey,
rows,
});
}
function addRow() {
const { rows, nextKey } = inputState;
rows.set(nextKey, "");
setInputState({
rows,
nextKey: nextKey + 1,
});
}
function deleteItem(k) {
const { rows, nextKey } = inputState;
rows.delete(k);
setInputState({
nextKey,
rows,
});
}
const uiList = [];
for (const [k, v] of inputState.rows.entries()) {
uiList.push(
<div key={k}>
<input
value={v}
onChange={({ target: { value } }) => {
onChange(k, value);
}}
/>
<button onClick={() => deleteItem(k)}>-</button>
</div>
);
}
return (
<>
{uiList}
<button onClick={addRow}>+ add row</button>
</>
);
}
UPDATE 3
Using plain array but with key property.
import React, { useState } from "react";
export default function() {
const [inputState, setInputState] = useState({
rows: [],
nextKey: Number.MIN_VALUE,
});
function onChange(i, v) {
const { rows, nextKey } = inputState;
rows[i].value = v;
setInputState({
nextKey,
rows,
});
}
function addRow() {
const { rows, nextKey } = inputState;
rows.push({ key: nextKey, value: "" });
setInputState({
rows,
nextKey: nextKey + 1,
});
}
function deleteItem(i) {
const { rows, nextKey } = inputState;
rows.splice(i, 1);
setInputState({
nextKey,
rows,
});
}
return (
<>
{inputState.rows.map(({ key, value }, index) => {
return (
<div key={key}>
<input
value={value}
onChange={({ target: { value } }) => {
onChange(index, value);
}}
/>
<button onClick={() => deleteItem(index)}>-</button>
</div>
);
})}
<button onClick={addRow}>+ add row</button>
</>
);
}
Instead of using external js, i just do the little modification of the code, instead of doing the map or filter, you can use directly with splice,
the below code will help to do the deletion fast and it will keep the same index after deletion also.
import React, { useState } from "react";
export default function() {
const [rows, setRows] = useState([""]);
const onChange = (e, i) => {
const {value} = e.target;
setRows(prev => {
prev[i] = value;
return [...prev];
});
};
const onDelete = i => {
setRows(prev => {
prev.splice(i,1, undefined);
return [...prev];
});
};
return (
<>
{rows.map((row, index) => (typeof row !== "undefined") && (
<div key={index}>
<input value={row} onChange={e => onChange(e, index)} />
{index !== 0 && (
<button onClick={() => onDelete(index)}>
- delete row
</button>
)}
</div>
))}
<button onClick={() => setRows(rows.concat([""]))}>
+ add row
</button>
</>
);
}
So far, best choices are
Generate key to use as key.
Use map to have efficient update and delete.
Don't mutate the state.
The immutable library fits the point 2 and 3 perfectly. Since it can update and delete without mutation efficiently it seems like a good choice.
import React, { useState, useEffect } from "react";
import { OrderedMap } from "immutable";
function UsingImmutableJS({ onChange }) {
const [inputState, setInputState] = useState({
rows: OrderedMap(),
nextKey: 0,
});
useEffect(() => {
if (onChange) onChange([...inputState.rows.values()]);
}, [inputState, onChange]);
function onInputChange(k, v) {
setInputState(prev => ({
...prev,
rows: prev.rows.update(k, () => v),
}));
}
function addRow() {
setInputState(prev => ({
nextKey: prev.nextKey + 1,
rows: prev.rows.set(prev.nextKey, ""),
}));
}
function deleteItem(k) {
setInputState(prev => ({
...prev,
rows: prev.rows.delete(k),
}));
}
return (
<>
{inputState.rows.entrySeq().map(([k, v]) => {
return (
<div key={k}>
<input
value={v}
onChange={({ target: { value } }) => {
onInputChange(k, value);
}}
/>
<button onClick={() => deleteItem(k)}>-</button>
</div>
);
})}
<button onClick={addRow}>+ add row</button>
<button onClick={() => console.log([...inputState.rows.values()])}>
print
</button>
</>
);
}
Without index we can do using the Map and Map retrieval is faster, below code you can try.
import React, { useState } from "react";
export default function() {
const [rows, setRows] = useState(new Map());
const onUpdate = (key, value) => {
setRows(prev => new Map(prev).set(key, { key, value}));
}
const onDelete = (key) => {
setRows(prev => new Map(prev).delete(key));
}
return (
<>
{[...rows].map((row, index) => (typeof row[1].value !== "undefined") && (
<div key={index}>
<input value={row[1].value} onChange={e => onUpdate(row[1].key, e.target.value)} />
<button onClick={() => onDelete(row[1].key)}>- delete row</button>
</div>
))}
<button onClick={() => onUpdate(rows.size, "")}>
+ add row
</button>
</>
);
}

Categories