How do I remove duplicate code from titles.map and titles.slice?
how should be handled with en effect to set the facets that should be displayed. Functionality works as expected, I just want to remove duplicated code.
import { useState } from "react";
const App = () => {
const titles = [
{ id: "0", name: "Title" },
{ id: "5637144579", name: "Miss" },
{ id: "5637144576", name: "Mr." },
{ id: "5637145326", name: "MrandMrs." },
{ id: "5637144577", name: "Mrs." },
{ id: "5637144578", name: "Ms." },
{ id: "5637145330", name: "Br." },
{ id: "5637145327", name: "Dame" },
{ id: "5637144585", name: "Dr." },
{ id: "5637145331", name: "Fr." },
{ id: "5637144582", name: "I" },
];
const [isAllFacets, setIsAllFacets] = useState(false);
const MAX_FACET_COUNT = 5;
const visibleFacetCount = titles.length - 1 === MAX_FACET_COUNT ?
titles.length :
MAX_FACET_COUNT;
const showAllFacet = () => { setIsAllFacets(!isAllFacets); };
return (<>
{isAllFacets ?
titles.map((title: any) => {
return <div key={title.id}>{title.name}</div>;
}) :
titles.slice(0, visibleFacetCount).map((title) => {
return <div key={title.id}>{title.name}</div>;
})}
{titles.length > visibleFacetCount && (<>
{!isAllFacets ? (
<button onClick={showAllFacet}>show all</button>
) : (
<button onClick={showAllFacet}>show less</button>
)}
</>)}
</>);
};
export default App;
One option would be to use the conditional operator to slice the entire existing array in case isAllFacets is false - instead of alternating over the whole JSX to return, alternate over only the index to slice.
A similar approach can be used to simplify your <button> text.
return (
<>
{
titles
.slice(0, isAllFacets ? titles.length : visibleFacetCount)
.map(title => <div key={title.id}>{title.name}</div>)
}
{titles.length > visibleFacetCount && <button onClick={showAllFacet}>show{isAllFacets ? ' all' : ' less'}</button>}
</>
);
You can achieve your goal with useMemo hook. Prepare the data to display and render it. Value will be recalculated when anything inside depsArray is changed.
const titlesToDisplay = useMemo(() => {
return isAllFacets ? titles : titles.slice(0, visibleFacetCount);
}, [titles, isAllFacets, visibleFacetCount]);
return (
<>
{titlesToDisplay.map((title) => {
return <div key={title.id}>{title.name}</div>;
})}
{titles.length > visibleFacetCount && (
<>
{!isAllFacets ? (
<button onClick={showAllFacet}>show all</button>
) : (
<button onClick={showAllFacet}>show less</button>
)}
</>
)}
</>
);
you only need to put all together :
titles.slice(0, isAllFacets?titles.length:visibleFacetCount).map((title) => { return <div key={title.id}>{title.name}
</div>; })
Create a renderTitle function which returns the title div
const renderTitle = title => <div key={title.id}>{title.name}</div>
Then pass it to both .map invocations
titles.map(renderTitle)
and
titles.slice(0, visibleFacetCount).map(renderTitle)
And for your button, simplify it with:
<button onClick={showAllFacet}>show {isAllFacets ? "less" : "all"}</button>
Related
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.
So, I'm trying toggle the Icon based on the isBadData per email data in the object of array. But I can't seem to find out how could save it back to the state so it can update the Icon image in LeadProfileComponent.
This is what it looks like:
checkIcon = isBadData: false
crossIcon = isBadData: true
Heres my code:
// ModalComponent.js
const [leadProfile, setLeadProfile] = useState([
{
id: 'd114877b-074b-4aa2-a3f0-3b9446885336',
firstName: 'wqe',
lastName: 'wqe',
name: 'wqe wqe',
email: [
{
type: 'personal',
address: 'qwe#hotmail.com',
valid_since: '2010-05-09',
isBadData: true,
},
{
type: 'personal',
address: 'wqe#hotmail.com',
valid_since: '2017-03-09',
isBadData: true,
},
{
type: 'personal',
address: 'wqe#aol.com',
valid_since: '2009-01-12',
isBadData: true,
},
],
},
]);
<LeadProfileComponent leadProfile={leadProfile} setLeadProfile={setLeadProfile} />
// LeadProfileComponent.js
const LeadProfileComponent = (props) => {
const handleChildEmail = (email, index) => {
props.setLeadProfile((prev: any) => {
const value = { ...prev[0].email[index] };
console.log('inside value');
console.log(value);
value.isBadData = !value.isBadData;
console.log(value);
// return prev;
return [value];
});
console.log('props.leadProfile');
console.log(props.leadProfile);
};
return (
<>
{
props.leadProfile.map((lead, index) => (
return(
<>
{lead.email.map(() => {
return (
<button
id="btnCheck"
onClick={() => {
handleChildEmail(email, index);
}}
>
<img
src={
email.isBadData !== true
? checkIcon
: closeIcon
}
/>
</button>
)
})}
</>
)
}
</>
);
}
Heres what it looks like when you console log inside of handChildEmail function:
As you can see, I was able to change the inside boolean of email[0], but I cant save it back to the leadProfile state since I have a missing part in the destructuring part
Break your components in smaller parts, and manage each email individually
LeadProfileEmailComponent.js
const LeadProfileEmailComponent = ({ initialEmailData, ...props }) => {
const [emailData, setEmailData] = useState(initialEmailData);
return (
<button
id="btnCheck"
onClick={() => {
setEmailData({
...emailData,
isBadData: !emailData.isBadData
});
}}
>
<img
src={
emailData.isBadData !== true
? checkIcon
: closeIcon
}
/>
</button>
)
}
Change this in LeadProfileComponent:
{lead.email.map((email) => {
return (
<LeadProfileEmailComponent initialEmailData={email} />
)
})}
The downside is, the state of the parent component will not be updated. However this is standard design pattern practise, you should not rely on the parent component data for this.
I am trying to implement toggle functionality, by using this functionality user can select desired single preference, and also the user can select all preferences by using the "Select All" button. I have implemented the code that is supporting a single selection I want to make select all functionality.
This is how i am handling toggle
const toggleItem = useCallback((isToggled, value) => {
if (isToggled) {
setToggledItems((prevState) => [...prevState, value]);
} else {
setToggledItems((prevState) => [...prevState.filter((item) => item !== value)]);
}
}, []);
const [toggledItems, setToggledItems] = useState([]);
var eventsnfo = [
{
icon: '',
title: 'select All',
subTitle: '',
},
{
icon: 'event1',
title: 'event1',
subTitle: 'event1desc',
},
{
icon: 'event2',
title: 'event2',
subTitle: 'event2desc',
},
{
icon: 'event3',
title: 'event3',
subTitle: 'event3desc',
},
{
icon: 'event4',
title: 'event4',
subTitle: 'event4desc',
},
];
this is how i am loading all toggle sections
<div>
{eventsnfo?.map(({ icon, title, subTitle }, index) => {
return (
<>
<div key={index} className='events__firstSection'>
<div className='events__agreeToAllContainer'>
{icon && (
<Icon name={icon} className='events__noticeIcon' isForceDarkMode />
)}
<div className={icon ? 'events__text' : 'events__text events__leftAlign '}>
{title}
</div>
</div>
<Toggle
containerClass='events__toggle'
checked={toggledItems.includes(title)}
onToggle={(isToggled) => toggleItem(isToggled, title)}
/>
</div>
{subTitle && <div className='events__description'>{subTitle}</div>}
<div className={index !== eventsnfo.length - 1 && 'events__divider'}></div>
</>
);
})}
</div>;
I think you can toggle all by changing your toggleItem function
const toggleItem = (isToggled, value) => {
let items = [...toggledItems];
if (isToggled) {
items =
value === "select All"
? eventsnfo?.map((events) => events.title)
: [...items, value];
if (items?.length === eventsnfo?.length - 1) {
items.push("select All");
}
} else {
items =
value === "select All"
? []
: [...items.filter((item) => item !== value && item !== "select All")];
}
setToggledItems(items);
};
Working Demo
I want to create something similar to an accordeon:
https://codesandbox.io/s/suspicious-golick-xrvt5?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const items = [
{
title: "lorem1",
content: "content1"
},
{
title: "lorem2",
content: "content2"
}
];
const [hide, setHide] = useState({
title: "",
open: false
});
const hideContent = (title) => {
setHide({
title: title,
open: !hide.open
});
};
return (
<div className="App">
{items.map((i) => {
return (
<div>
<h1 onClick={() => hideContent(i.title)}>{i.title}</h1>
{hide.title === i.title && hide.open ? <p>{i.content}</p> : ""}
</div>
);
})}
</div>
);
}
Now when i click on a title its content appears, but when click on another title the first content disappear but the actual clicked does not appear. How to click on the second title and to hide the previous content and in the same time to open the actual clicked item content?
I think instead of going with open state. Go with id. You item state
const items = [
{
id: 0,
title: "lorem1",
content: "content1"
},
{
id: 1,
title: "lorem2",
content: "content2"
}
];
Pass id in hideContent:
const hideContent = (title, id) => {
setHide(
(prev) =>
({
title: title,
open: prev.open === id ? null : id
})
);
}
and condition to check inside return like this:
hide.title === i.title && hide.open === i.id ? (
<p>{i.content}</p>
) : (
""
)}
here is demo and full code: https://codesandbox.io/s/upbeat-brattain-8yfx7?file=/src/App.js
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.