React array state is not Iterable - reactjs

I am trying to figure out how to pass an item thru the state on the item: [] inside the list state. Whenever I tried this code, an error shows up as lists is not iterable whenever I insert or add item to the array
Is there a way to insert data to the array property of the state? And adding more string arrays in that property?
const [lists, setLists] = useState({
item: [],
});
const addList = () => {
const listItem = document.getElementById("listItem");
if (listItem.value !== "") {
setLists([
...lists,
{
item: listItem.value,
},
]); // >>> [INSERT TO THE ARRAY PROPERTY]
listItem.value = "";
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
id="listItem"
name="item"
onKeyPress={(e) => (e.key === "Enter" ? addList() : null)}
/>
<button
type="button"
onClick={() => {
addList();
}}
>
Add
</button>
<ul>
LIST
{lists.item.map((val, index) => {
return (
<li key={index}>
<p>{val}</p>
<button type="button" onClick={() => removeList(index)}>
Remove
</button>
</li>
);
})}
</ul>
<button type="submit">submit</button>
</form>
</div>
);

You seem to be having some confusion regarding your data types. lists is an array of objects of the shape {item: ...}.
The useState call should be useState([]).
You'll need lists.map(({item}, index) => (or lists.map(val and val.item) to get at the ....
You can use e.g. console.log(lists), or a debugger, to see what's really happening.)
You shouldn't use document.getElementById() with React, ever. Instead, make the input controlled (or have a ref to it and read the value if you want uncontrolled, but you likely don't).
The setLists call should be the functional form: setLists(lists => [...lists, {item: listItem.value}]).
All in all, something like
function Component() {
const [newItemText, setNewItemText] = React.useState("");
const [todoList, setTodoList] = React.useState([]);
const addList = (event) => {
event.preventDefault();
if (newItemText !== "") {
setTodoList(todoList => [
...todoList,
{
item: newItemText,
},
]);
setNewItemText("");
}
};
return (
<div>
<form onSubmit={addList}>
<input
type="text"
name="item"
value={newItemText}
onChange={e => setNewItemText(e.target.value)}
/>
<button
type="submit"
>
Add
</button>
</form>
<ul>
LIST
{todoList.map(({item}, index) => {
return (
<li key={index}>
<p>{item}</p>
<button type="button">
Remove
</button>
</li>
);
})}
</ul>
</div>
);
}
ReactDOM.render(<Component />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>
<div id="root">

const [lists, setLists] = useState({
item: [],
});
In the code above you set initial value as an Object not an Array. Try to change code like below.
const [lists, setLists] = useState([]);

Related

How i can refresh this function on started value

Hi! i have a problem with my state in React, I have two onMouse functions, the first one is to add an element and the second one is to delete, unfortunately the second one does not delete and the added element 'opacity' is rendered.
let menuItems = ['Tasks', 'Issues', 'Files', 'Raports']
const [item, setItem] = useState(menuItems)
const handleSpace = (id) => {
menuItems.splice(id, 0, 'opacity')
setItem([...item])
}
const restart = () => {
menuItems = ['Tasks', 'Issues', 'Files', 'Raports']
setItem([...item])
}
return (
<>
<div className="dashboard" style={slide.flag ? {left: '-105%'} : {left: '0%'}}>
<div className="dashboard__nav">
<ul className="dashboard__nav-list">
{item.map((item, id) => {
return <li className="dashboard__nav-item" key={id} onMouseOver={() => handleSpace(id)} onMouseLeave={restart}>{item}</li>
})}
</ul>
</div>
<div className="dashboard__array">
{tasks.map((task, id) => {
return (
<div className="dashboard__array-item" key={id}>
<div className="dashboard__array-item-header">
<p className="dashboard__array-item-header-title">{task}</p>
<button className="dashboard__array-item-header-cancel">
<FontAwesomeIcon icon={faCancel} />
</button>
</div>
<div className="dashboard__array-item-main">
<p className="dashboard__array-item-main-description">{descriptionTasks[id]}</p>
<p className="dashboard__array-item-main-button">Show More</p>
</div>
</div>
)
})}
</div>
</div>
</>
)
I already created setItem(menuItems), it removed the element 'opacity, but juz it didn't add it a second time
It seems that the two functions might be over complicating the handling of the item state.
Try handle setItem without changing another variable menuItems, so it can be used as a reset value at anytime.
Example:
const menuItems = ["Tasks", "Issues", "Files", "Raports"];
const [item, setItem] = useState(menuItems);
const handleSpace = (id) =>
setItem((prev) => {
const newItems = [...prev];
newItems.splice(id, 0, "opacity");
return newItems;
});
const restart = () => setItem(menuItems);
Hope this will help.

Rendering an array of objects in React but it says objects cant be child elements

I am storing the response from an API in an array called Products. This array stores the objects returned. I am displaying the title property of the objects in a div. But getting objects cant be child elements in React error. What should I do?
Also, I have tried rendering the products title (eg: Products[0].title) but even this doesn't work. But this is not an object. So why doesn't this work?
Code on React:
const App = () => {
const [Elec,setElec] = useState("");
const [Jewe,setJewe] = useState("");
const [MJ,setMJ] = useState("");
const [WMJ,setWMJ] = useState("");
const [Products,setProducts] = useState([]);
const fetcher = (e) => {
fetch('https://fakestoreapi.com/products/categories')
.then(res=>res.json())
.then(json=>{
console.log(json);
setElec(json[0]);
setJewe(json[1]);
setMJ(json[2]);
setWMJ(json[3]);
console.log(setElec);
})
}
const jewelleryproducts = () =>{
fetch('https://fakestoreapi.com/products/category/jewelery')
.then(res=>res.json())
.then(json=>{console.log(json);
setProducts(json);
console.log(Products);
})
}
return(
<>
<Head>
<link rel="stylesheet" href="./css/general.css"></link>
</Head>
<div>
<div className="searchbardiv">
<input type="text" placeholder="Search"></input>
<img src="./images/cart.png" typ></img>
<img src="./images/avatar.jpg"></img>
</div>
<div className="categories">
<div> <button
type="submit"
onClick={e => {fetcher()}}
>Show all categories</button></div>
<ol>
<li><a>{MJ}</a> </li>
<li><a>{WMJ}</a></li>
<li><a>{Elec}</a> </li>
<li><button type="submit" onClick={ e => {jewelleryproducts()}}>{Jewe}</button></li>
</ol>
</div>
<div className="jewelleryproducts">
<div>{Products[0]}</div>
<div>{Products[1]}</div>
<div>{Products[2]}</div>
<div>{Products[3]}</div>
</div>
</div>
</>
)
}
export default App;
you can do this
<Select>
{list.map((el) => (
<Option key={el.code} value={=el.code}>
{el.name}
</Option>
))}
</Select>
You need to .map() over the array and define what you should be displayed for every iteration.
Example:
<div className="jewelleryproducts">
{Products.map(product => (
<div>{product}</div>
)}
</div>
This also prevents the code duplication.
Note: this example is based on the assumption that each product is a string.
If that's not the case, then it should be adjusted accordingly.

React List and Keys - How to make last added item appearing at the top?

I am working on React list and keys. But I would like to make the last added item appearing at the top.
is there any one who can help me?
Example.
import { useState } from "react";
function ListsKeys() {
const [names, setNames] = useState([]);
function handleInput() {
let input = document.getElementById("input");
setNames((prevState) => {
let state = [...prevState];
state.push(input.value);
return state;
});
}
return (
<div className="container">
<h2> 03// Lists& Keys </h2>
<hr />
<h4>- Lists & Keys</h4>
<input id="input" />
<button className="btn" onClick={handleInput}>
{" "}
Submit Name{" "}
</button>
{names.map((name) => (
<div className=""> {name}</div>
))}
</div>
);
}
export default ListsKeys;
There are a couple ways to build "reversed" array.
Build up the array in state in the order you want by inserting new elements at the front of the list instead of at the end.
function ListsKeys() {
const [names, setNames] = useState([]);
function handleInput() {
const input = document.getElementById("input");
setNames((prevState) => {
return [input.value, ...prevState]; // prepend new value
});
}
return (
<div className="container">
<h2> 03// Lists& Keys </h2>
<hr />
<h4>- Lists & Keys</h4>
<input id="input" />
<button className="btn" onClick={handleInput}>
Submit Name
</button>
{names.map((name) => (
<div className="">{name}</div>
))}
</div>
);
}
Insert at the end as per normal and just reverse the array when rendering.
function ListsKeys() {
const [names, setNames] = useState([]);
function handleInput() {
const input = document.getElementById("input");
setNames((prevState) => {
return [...prevState, input.value]; // append new value
});
}
return (
<div className="container">
<h2> 03// Lists& Keys </h2>
<hr />
<h4>- Lists & Keys</h4>
<input id="input" />
<button className="btn" onClick={handleInput}>
Submit Name
</button>
{names
.slice() // copy
.reverse() // reverse
.map((name) => (
<div className="">{name}</div>
))}
</div>
);
}
Also, querying the DOM via document.getElementById is anti-pattern in React, instead you should use a React ref and access the DOMNode via an attached ref's current value.
function ListsKeys() {
const inputRef = React.useRef();
const [names, setNames] = useState([]);
function handleInput() {
const { target } = inputrRef.current;
setNames((prevState) => {
return [target.value, ...prevState]; // prepend new value
});
target.value = ""; // clear input
}
return (
<div className="container">
<h2> 03// Lists& Keys </h2>
<hr />
<h4>- Lists & Keys</h4>
<input ref={inputRef} />
<button className="btn" onClick={handleInput}>
Submit Name
</button>
{names.map((name) => (
<div className="">{name}</div>
))}
</div>
);
}
Or better to just use local component state and convert the input to be fully controlled.
function ListsKeys() {
const [name, setName] = useState('');
const [names, setNames] = useState([]);
function handleInput() {
setNames((prevState) => {
return [name, ...prevState]; // prepend new value
});
setName(''); // clear input
}
return (
<div className="container">
<h2> 03// Lists& Keys </h2>
<hr />
<h4>- Lists & Keys</h4>
<input
value={name}
onChange={e => setName(e.target.value)}
/>
<button className="btn" onClick={handleInput}>
Submit Name
</button>
{names.map((name) => (
<div className="">{name}</div>
))}
</div>
);
}
You can use the Array.prototype.reverse() function before print the list and add items using Array.prototype.unshift() passing as argument the new item.
const arr = [1,2,3,4,5].reverse(); //[5,4,3,2,1]
Here is the details
const numbers = [1, 2, 3, 4, 5].reverse();
const listItems = numbers.map((numbers) =>
<li>{numbers}</li>
);
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);

Jest, Enzyme, Cannot read property 'map' of undefined

Let's say, there I am trying to add a new item into my todo list and checking if added this item correctly, with Jest and Enzyme.
First, I have tried to reach that item with defining a class to List, didn't work;
wrapper.find('#toDoInputId').simulate("change", { target: { value: "testFirst"}})
const listViewerWrapper = shallow(<ListItems/>);
const input = wrapper.find('.list');
const result = input.text();
Then I have tried to reach out directly in the document;
const testFirst = getByText(/testFirst/i);
expect(testFirst).toBeInTheDocument();
But sadly none of these has worked and having the following error;
TypeError: Cannot read property 'map' of undefined
6 | function ListItems(props){
7 | const items= props.items;
> 8 | const listItems = items.map(item =>
| ^
9 | {
10 | return <div className="list" key={item.id}>
11 | <p>
My list item js;
function ListItems(props){
const items= props.items;
const listItems = items.map(item =>
{
return <div className="list" key={item.id}>
<p>
<input
type="checkbox"
onClick={ () => props.handleClickbox(item.id)} />
<input style={{ color:"white", textDecoration: item.completed ? "line-through":null}}
type="text"
id={item.id}
value={item.task}
onChange ={
(e) =>{
props.setUpdate(e.target.value, item.id)
}
}/>
<span>
<FontAwesomeIcon className="faicons"
icon='trash'
onClick={ () => props.deleteItem(item.id) } />
</span>
</p>
</div>
})
return(
<div>
<FlipMove duration={500} easing="ease-in-out" >
{listItems}
</FlipMove>
</div>
)
}
And the related part on App.js;
render() {
return (
<div className="App">
<header>
<form id="to-do-form" onSubmit={this.addItem}>
<input
id="toDoInputId"
type="text" placeholder="Enter task"
value={this.state.currentItem.text}
onChange={this.handleInput}/>
<button type="submit"> Add </button>
</form>
</header>
<ListItems items={this.state.items}
deleteItem = {this.deleteItem}
handleClickbox = {this.handleClickbox}></ListItems>
</div>
)
}
Thanks for any advice or answer. Happy coding!
You aren't passing the items prop when you shallow render ListItems.
Update the code to pass items array in props and you won't see the undefined error.
const testItems = [{ id: 1, task: 'todo', completed: false}, { id: 2, task: 'second todo', completed: true }];
const listViewerWrapper = shallow(<ListItems items={testItems} />);

React Hook select multiple items with icons

I am trying to implement a popover with many items where a user can multi select them. When a user clicks a item, a font-awesome icon is shown to the right.
A user is able to select multiple items and an icon is shown on the right to show it has been checked. This icon toggles when clicking. My problem is my event handler is tied to all the items and whenever I click one, all gets checked.
I am new to hook and react. I am also trying to assign the Id of the selected item in an array. It won't append.
const SettingsComponent = (props) => {
const urlStofTyper = stofTyperUrl;
const stofTyper = [];
const [isPopoverOpen, setPopoverOpen] = useState(false);
const [isItemChecked, setItemChecked] = useState(false);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(null);
const [stoftype, setStoftyper] = useState({ DataList: [] });
const toggle = () => setPopoverOpen(!isPopoverOpen);
const sectionClicked = (e) => {
setItemChecked(!isItemChecked);
let secId = e.target.parentNode.getAttribute("data-section");
if (!isItemChecked) {
stofTyper.push(secId);
} else {
stofTyper.filter((sec) => sec == secId);
}
};
useEffect(() => {
fetchStoftyper({ setError, setLoading, setStoftyper });
}, []);
const fetchStoftyper = async ({ setError, setLoading, setStoftyper }) => {
try {
setLoading(true);
const response = await Axios(urlStofTyper);
const allStofs = response.data;
setLoading(false);
setStoftyper(allStofs);
} catch (error) {
setLoading(false);
setError(error);
}
};
return (
<React.Fragment>
<div className='list-header-icons__fontawesome-icon' id='PopoverClick'>
<FontAwesomeIcon icon={faCog} />
</div>
<Popover
isOpen={isPopoverOpen}
placement='bottom'
toggle={toggle}
target='PopoverClick'>
<PopoverHeader>formatter</PopoverHeader>
<div className='popover-body'>
<ul className='individual-col--my-dropdown-menu-settings'>
{stoftype.DataList.map((item) => (
<li key={item.Id} className='section-items'>
<a
onClick={sectionClicked}
className='dropdown-item'
data-section={item.Sections[0].SectionId}
data-format={
item.Formats.length > 0
? item.Formats[0].FormatId
: ""
}
aria-selected='false'>
<span className='formatter-name'>
{item.Name}
</span>
{isItemChecked && (
<span className='formatter-check-icon'>
<FontAwesomeIcon icon={faCheck} size='lg' />
</span>
)}
</a>
</li>
))}
</ul>
</div>
</Popover>
</React.Fragment>
);
Right now you are using one boolean variable to check if the icon should be displayed, it will not work because every item in your DataList should have it's own individual indicator.
One of the possible solution is to use new Map() for this purposes and store item.id as and index and true/false as a value, so your selected state will be something like this:
Map(3) {1 => true, 2 => true, 3 => false}
After that you can check if you should display your icon as follow:
!!selected.get(item.id)
It will return true if the value in your HashTable is true, and false if it's false or doesn't exist at all. That should be enough to implement the feature you asked for.
For the real example you can check flatlist-selectable section from official Facebook docs They show how to achieve multi-selection using this technique. Hope it helps.
Finally, I have a solution for my own question. Even though, I did not need above solution but I though it would be a good practice to try solve it. Popover is not relevant as that was used only as a wrapper. The below solution can still be placed in a Popover.
CodeSandBox link: Demo here using Class component, I will try to re-write this using hooks as soon as possible.
This solution dependency is Bootstrap 4 and Fontawesome.
import React, { Component } from "react";
import Cars from "./DataSeed";
class DropDownWithSelect extends Component {
constructor(props) {
super(props);
this.toggleDropdown = this.toggleDropdown.bind(this);
this.state = {
isDropDownOpen: false,
idsOfItemClicked: []
};
}
toggleDropdown = evt => {
this.setState({
isDropDownOpen: !this.state.isDropDownOpen
});
};
toggleItemClicked = id => {
this.setState(state => {
const idsOfItemClicked = state.idsOfItemClicked.includes(id)
? state.idsOfItemClicked.filter(x => x !== id)
: [...state.idsOfItemClicked, id];
return {
idsOfItemClicked
};
});
};
render() {
return (
<div className="dropdown">
<button
onClick={this.toggleDropdown}
className="btn btn-secondary dropdown-toggle"
type="button"
id="dropdownMenuButton"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
Dropdown button
</button>
{this.state.isDropDownOpen && (
<div
className="dropdown-menu show"
aria-labelledby="dropdownMenuButton"
>
{Cars.map(x => {
return (
<div
key={x.id}
onClick={() => this.toggleItemClicked(x.id)}
className="dropdown-item d-inline-flex justify-content-between"
>
<span className="d-flex"> {x.name} </span>
<span className="d-flex align-self-center">
{this.state.idsOfItemClicked.includes(x.id) && (
<i className="fa fa-times" aria-hidden="true" />
)}
</span>
</div>
);
})}
</div>
)}
</div>
);
}
}
export default DropDownWithSelect;
Data i.e., ./DataSeed
const cars = [
{
id: 1,
name: "BMW",
cost: 450000
},
{
id: 2,
name: "Audi",
cost: 430000
},
{
id: 3,
name: "Mercedes",
cost: 430000
}
];
export default cars;

Resources