how do I map over the following array of arrays and show each data contained in the objects separately? i.e I want the objects on the first array to be shown when a row on a table is expanded, the ones in the second array should be shown when another row is expanded and so on.
try to map like this:
array.map(subarray =>
subarray.map(item => ({ name: item.name, quantity: item.quantity })));
Why not loop over them and depict it as collapses?
import { useState } from "react";
const Collapse = ({ data, index }) => {
const [isOpen, setOpen] = useState(false);
const children = data.map(({ name, quantity }, key) => {
return (
<div key={key}>
<span>{`${name}:`}</span>
<span>{quantity}</span>
</div>
);
});
return (
<div>
<button onClick={() => setOpen(!isOpen)}>{`List ${index}`}</button>
<div>{isOpen && children}</div>
</div>
);
};
export default function App() {
const data = [
[
{ name: "Tusker", quantity: 9 },
{ name: "Guiness", quantity: 9 }
],
[
{ name: "Choma", quantity: 9 },
{ name: "Sprite", quantity: 9 }
]
];
return (
<div>
{data.map((localList, index) => {
return <Collapse key={index} data={localList} index={index} />;
})}
</div>
);
}
You might also check, if the collapse should be displayed at all - there might not always be data available.
This example isn't styled at all but it should lead you into the right direction.
Related
I have array of objects (items) with button clickHandler function. When you click on button, it should add that object to new array named ‘myNewArray’. Please help me to achieve this. I added demo object inside array ‘myNewArray’.
Explaination: If i click on category button '1-furniture', that object will added to new array named 'myNewArray'
import React, { useState } from "react";
const App = () => {
const [items, setItems] = useState([
{ name: "Furniture", categoryKey: 1 },
{ name: "Shoes", categoryKey: 2 },
{ name: "Electronics", categoryKey: 3 },
{ name: "Clothes", categoryKey: 4 },
{ name: "Grocery", categoryKey: 5 },
]);
const [myNewArray, setMyNewArray] = useState([{ name: "demo-item", categoryKey: 100 }]);
const clickHandler = (categoryKey: any) => {
console.log(categoryKey);
};
return (
<div>
{items.map((item) => (
<button onClick={() => clickHandler(item.categoryKey)} key={item.categoryKey}>
{item.categoryKey}-{item.name}
</button>
))}
<h4>New array after clicking on item from above array[items]</h4>
{myNewArray.map((item) => (
<button onClick={() => clickHandler(item.categoryKey)} key={item.categoryKey}>
{item.categoryKey}-{item.name}
</button>
))}
</div>
);
};
export default App;
just use set method in the useState
const clickHandler = (item: any) => {
setMyNewArray(prev => [...prev, {name:item.name,categoryKey: item.categoryKey}])
};
and pass item in the click
onClick={() => clickHandler(item)}
Here's the working solution:
import React, { useState, useEffect } from "react";
const App = () => {
const [items, setItems] = useState([
{ name: "Furniture", categoryKey: 1 },
{ name: "Shoes", categoryKey: 2 },
{ name: "Electronics", categoryKey: 3 },
{ name: "Clothes", categoryKey: 4 },
{ name: "Grocery", categoryKey: 5 }
]);
const [myNewArray, setMyNewArray] = useState([
{ name: "demo-item", categoryKey: 100 }
]);
useEffect(() => {
console.log(myNewArray);
}, [myNewArray]);
const clickHandler = (item) => {
setMyNewArray([...myNewArray, item]);
};
return (
<div>
{items.map((item) => (
<button onClick={() => clickHandler(item)} key={item.categoryKey}>
{item.categoryKey}-{item.name}
</button>
))}
<h4>New array after clicking on item from above array[items]</h4>
{myNewArray.map((item, i) => (
<button key={i}>
{item.categoryKey}-{item.name}
</button>
))}
</div>
);
};
export default App;
The live demo is here: https://codesandbox.io/s/determined-solomon-phyy9u?file=/src/App.js:0-1163
You can have a look at the console to check the myNewArray updates.
You could also do like this.
if Item is with the matching category Key. Then, it's Update the myNewArray state with the new item
const clickHandler = (categoryKey: any) => {
const item = items.find((i) => i.categoryKey === categoryKey);
setMyNewArray([...myNewArray, item]);
};
Here is Codesandbox
i'm building a checkbox todo List that checked checkbox is disappearing.
I have two problems.
checked checkbox remains after re-rendering
When the two checkboxes are left, they dont disappear.
Here is my codeSandBox:-----
I think this might be a setState issue, setItems([...items.filter((item) => !checkedItems[item.id])]); -> rerendering (this scope havecheckedItems ={false,true,false,false,false}) so, checkbox is remaining?
import "./styles.css";
import React from "react";
const todos = [
{ id: 0, value: "Wash the dishes" },
{ id: 1, value: "have lunch" },
{ id: 2, value: "listen to music" },
{ id: 3, value: "running" },
{ id: 4, value: "work out" }
];
export default function App() {
const [items, setItems] = React.useState(todos);
const [checkedItems, setCheckedItems] = React.useState(
new Array(todos.length).fill(false)
);
const checkHandler = (idx) => {
checkedItems[idx] = !checkedItems[idx];
setItems([...items.filter((item) => !checkedItems[item.id])]);
setCheckedItems([...checkedItems]);
};
return (
<div className="App">
{items.map((todo, idx) => (
<div key={idx}>
<span>{todo.value}</span>
<input
type="checkbox"
checked={checkedItems[idx]}
onChange={() => checkHandler(idx)}
></input>
</div>
))}
</div>
);
}
It is not good practice to use index as key when iterating objects.
Since you have id, use this.
{items.map(todo => (
<div key={todo.id}>
<span>{todo.value}</span>
<input
type="checkbox"
checked={checkedItems[todo.id]}
onChange={() => checkHandler(todo.id)}
></input>
</div>
))}
You mess with indexes and the result is confusing.
If you want the items to be persisted and shown, just remove this line
setItems([...items.filter((item) => !checkedItems[item.id])]);
Demo
You do not need to have a separate checkedItems state. You can add a field checked in your todo object.
const todos = [
{ id: 0, value: "Wash the dishes", checked: false },
{ id: 1, value: "have lunch", checked: false },
{ id: 2, value: "listen to music", checked: false },
{ id: 3, value: "running", checked: false },
{ id: 4, value: "work out", checked: false }
];
export default function App() {
const [items, setItems] = React.useState(todos);
const checkHandler = (idx) => {
setItems(items.filter((item) => item.id !== idx));
};
return (
<div className="App">
{items.map((todo, idx) => (
<div key={idx}>
<span>{todo.value}</span>
<input
type="checkbox"
checked={todo.checked}
onChange={() => checkHandler(todo.id)}
></input>
</div>
))}
</div>
);
}
The key mistake you're making here is in onChange={() => checkHandler(idx)} and then using idx as your variable to filter out your items. idx does NOT equal the ids you have in your todo list, it will change based on the number of items left in the array.
The filter can also be improved to just be
setItems([...items.filter((item) => item.id !== idx)]);
The final code should look something like this (I'm not sure what checkedItems is meant to be doing or what it's for so it's not a consideration in this answer).
import "./styles.css";
import React from "react";
const todos = [
{ id: 0, value: "Wash the dishes" },
{ id: 1, value: "have lunch" },
{ id: 2, value: "listen to music" },
{ id: 3, value: "running" },
{ id: 4, value: "work out" }
];
export default function App() {
const [items, setItems] = React.useState(todos);
const [checkedItems, setCheckedItems] = React.useState(
new Array(todos.length).fill(false)
);
const checkHandler = (idx) => {
checkedItems[idx] = !checkedItems[idx];
setItems([...items.filter((item) => item.id !== idx)]);
setCheckedItems([...checkedItems]);
};
return (
<div className="App">
{items.map((todo, idx) => (
<div key={idx}>
<span>{todo.value}</span>
<input
type="checkbox"
onChange={() => checkHandler(todo.id)}
></input>
</div>
))}
</div>
);
}
Simply remove this line:
setItems([...items.filter((item) => !checkedItems[item.id])]);
that causes the list items to be filtered.
im making a portfolio website and have multiple different buttons with skills which contain an img and p tag. I want to show the description of each tag everytime a user clicks on the button. how can I do this? right now everytime user clicks it, all buttons show description.
const Skills = () => {
const [state, setState] = useState(false)
let skills = [
{ id: 1, desc: 'HTML5', state: false, img: htmlIcon },
{ id: 2, desc: 'CSS3', state: false, img: cssIcon },
{ etc....}
const showDesc = (id) => {
console.log(skills[id-1] = !state);
setState(!state)
}
return (
{skills.map(skill => (
<button onClick={(id) => showDesc(skill.id)}>
<img style={ state ? {display:'none'} : {display:'block'}} src={skill.img} />
<p style={ state ? {display:'block'} : {display:'none'}}>{skill.desc}</p>
</button>
))}
I recommend to manipulate element state instead of entry list. But if you really need to manipulate entry list you should add that list to your state. Then when you want to show/hide specific item, you need to find that item in state and correctly update entry list by making a copy of that list (with updated item). For example you can do it like this:
import React, { useState } from 'react';
const Skills = () => {
const [skills, setSkills] = useState([
{
id: 1,
desc: 'HTML5',
state: false,
img: htmlIcon, // your icon
},
{
id: 2,
desc: 'CSS3',
state: false,
img: cssIcon, // your icon
},
]);
const showDesc = (id) => {
const newSkills = skills.map((item) => {
if (item.id === id) {
return {
...item,
state: !item.state,
};
}
return item;
});
setSkills(newSkills);
};
return (
<div>
{skills.map(({
id,
img,
state,
desc,
}) => (
<button type="button" key={id} onClick={() => showDesc(id)}>
<img alt="img" style={state ? { display: 'none' } : { display: 'block' }} src={img} />
<p style={state ? { display: 'block' } : { display: 'none' }}>{desc}</p>
</button>
))}
</div>
);
};
Instead of manipulating all list, you can try to move show/hide visibility to list item itself. Create separate component for item and separate component for rendering that items. It will help you to simplify logic and make individual component responsible for it visibility.
About list rendering you can read more here
For example you can try something like this as alternative:
import React, { useState } from 'react';
const skills = [
{
id: 1,
desc: 'HTML5',
img: htmlIcon, // your icon
},
{
id: 2,
desc: 'CSS3',
img: cssIcon, // your icon
},
];
const SkillItem = ({
img,
desc = '',
}) => {
const [visibility, setVisibility] = useState(false);
const toggleVisibility = () => {
setVisibility(!visibility);
};
const content = visibility
? <p>{desc}</p>
: <img alt="img" src={img} />;
return (
<div>
<button type="button" onClick={toggleVisibility}>
{content}
</button>
</div>
);
};
const SkillList = () => skills.map(({
id,
img,
desc,
}) => <SkillItem img={img} desc={desc} key={id} />);
I have some CMS content being returned and my goal is to have a year slider controlling the content depending on the year that the user selects by clicking the minus/plus arrow.
This is my code:
import "./styles.css";
import React from "react";
export default function App() {
return (
<div className="App">
<DatesProvider>
{data.map((item, index) => {
const Slice = slices[item.type];
return <Slice section={item.section} key={index} />;
})}
</DatesProvider>
</div>
);
}
const DateContext = React.createContext({});
const DatesProvider = ({ children }) => {
const [dates, setDates] = React.useState({});
return (
<DateContext.Provider value={{ dates, setDates }}>
{children}
</DateContext.Provider>
);
};
const DatePicker = ({ section }) => {
const { dates, setDates } = React.useContext(DateContext);
React.useEffect(() => {
// Set initial date
setDates((prevDates) => {
prevDates[section] = 2021;
return { ...prevDates };
});
// Clean up on dismount
return () => {
setDates((prevDates) => {
delete prevDates[section];
return { ...prevDates };
});
};
}, []);
const handlePlus = () => {
setDates((prevDates) => ({
...prevDates,
[section]: prevDates[section] + 1
}));
};
const handleMinus = () => {
setDates((prevDates) => ({
...prevDates,
[section]: prevDates[section] - 1
}));
};
return (
<div style={{ marginTop: 30 }}>
<button onClick={handleMinus}>-</button>
<span>{dates[section]}</span>
<button onClick={handlePlus}>+</button>
</div>
);
};
const Item = ({ section }) => {
const { dates } = React.useContext(DateContext);
return (
<div>
Section: {section} | Year: {dates[section]}
</div>
);
};
const data = [
{ type: "DatePicker", section: "foo" },
{ type: "Item", section: "foo" },
{ type: "Item", section: "foo" },
{ type: "DatePicker", section: "bar" },
{ type: "Item", section: "bar" },
{ type: "Item", section: "bar" }
];
const slices = { DatePicker, Item };
The result is currently this:
As you can tell it's returning the year slider several times and the structure is similar to this:
<slider> - 2021 + </slider>
<section class= "container-of-all-items">
<all-items></all-items>
</section>
<slider> - 2021 + </slider>
<section class= "container-of-all-items">
<all-items></all-items>
</section>
My goal is to have only one year slider wrapping/controlling the whole content items rather than the above repetition of sliders:
<slider> - 2021 + </slider>
<section class= "container-of-all-items">
<all-items></all-items>
</section>
Any idea how to achieve it by maintaining a map through the Slices?
I see, took me a while to understand, you basically want to have one set of + and - but list of items.
Then in your case, you code actually simplifies.
function Lists() {
const { dates, setDates } = React.useContext(DateContext);
const onClick = () => { setDates(...) }
return (
<>
<div onClick={onClick}>+</div>
<>
{dates.map((item, index) => {
return <Slice section={item.section} key={index} />
})}
</>
<div>-</div>
</div>
);
}
Then change your App.
export default function App() {
return (
<div className="App">
<DatesProvider value={...}>
<Lists />
</DatesProvider>
</div>
);
}
Actually you might not need the context at all, since the logic has been promoted to the parent. But it's up to you.
I'm following a React course and I'm trying to do some experiments around the code to have a better understanding of concepts.
I have some dummy data:
export const data = [
{ id: 1, name: 'john' },
{ id: 2, name: 'peter' },
{ id: 3, name: 'susan' },
{ id: 4, name: 'anna' },
];
and this is my component:
import React from "react";
import { data } from "../../../data";
const UseStateArray = () => {
const [people, setPeople] = React.useState(data);
return (
<>
{people.map((person) => {
const { id, name } = person;
return (
<div key={id} className="item">
<h4>{name}</h4>
</div>
);
})}
<button
type="button"
className="btn"
onClick={() => setPeople([])}
>
Clear Items
</button>
</>
);
};
export default UseStateArray;
The button has an event handler on click which calls setPeople with an empty array (so to remove all of the elements).
I was trying to change the funcionality of such button, trying to change the name of the first element of my array of objects (data) in the following way:
onClick={() => setPeople(people[0].name = 'Frank')}
Doing this, get an error, namely: "TypeError: people.map is not a function".
I think the reason is because I'm not returning an array anymore and therefore map fails to run.
How can I simply change the name (or any value) of an object present in an array?
You are mutating the object
clickHandler = () => {
const newPeople = people.map((person, index) => {
if (index === 0) {
return {
...person,
name: 'Frank'
}
}
return person;
});
setPeople(newPeople);
}
....
....
onClick={clickHandler}
You need to copy the array into a newer version.
Extract the object out of the array using the index property.
Update the field.
function App() {
const [data, setData] = React.useState([
{ name: "Hello", id: 1 },
{ name: "World", id: 2 }
]);
function changeName(idx) {
const newData = [...data];
newData[idx].name = "StackOverFlow";
setData(newData);
}
return (
<div>
{data.map((d, idx) => {
return (
<div>
<p>{d.name}</p>
<button
onClick={() => {
changeName(idx);
}}
/>
</div>
);
})}
</div>
);
}
NOTE :-
Mutation is not allowed on the state, which basically means you cannot change the state directly. Copy the object or an array and do the updates.