Toggle issue in React Js using Hooks - reactjs

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

Related

How do I remove duplicate code from titles.map and titles.slice?

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>

React Card Example issue - Card is replaced instead of being appended to a list of cards in another column

I have been persistently working on this problem where the goal is to drag a card form 'Column 1' and copy that into another column say 'Column 2'.
Now when my first card is dragged and drop it into 'Column 2, the card is accordingly added to that column, but when I drag another card and drop into 'Column 2' instead of being appended it just replaces the existing card with itself.
I have been debugging the state, but the issue still persists. I haven't gotten a clue what am I doing wrong here?
Here's my code
// Card Component
function Card({ id, text, isDrag }) {
const [, drag] = useDrag(() => ({
type: "bp-card",
item: () => {
return { id, text}
},
collect: monitor => ({
isDragging: !!monitor.isDragging(),
}),
canDrag: () => isDrag
}));
return (
<div
className='card'
ref={drag}
style={{
cursor: isDrag ? 'pointer' : 'no-drop'
}}
>
{text}
</div>
)
}
// Column Component
function Column({ title, children, onCardDropped }) {
const [, drop] = useDrop(() => ({
accept: "bp-card",
drop: item => {
onCardDropped(item);
}
}));
return (
<div className="flex-item" ref={title === 'Column 2' ? drop : null}>
<p>{title}</p>
{children.length > 0 && children.map(({ id, text, isDrag }) => (
<Card
key={id}
id={id}
text={text}
isDrag={isDrag}
/>
))}
</div>
)
}
// Main App
function App() {
const [cards] = useState([
{ id: 1, text: 'Card 1', isDrag: true },
{ id: 2, text: 'Card 2', isDrag: true },
]);
const [columns, setColumns] = useState([
{
id: 1,
title: 'Column 1',
children: cards
},
{
id: 2,
title: 'Column 2',
children: []
},
]);
const onCardDropped = ({ id, text }) => {
// let card = null;
const targetColumnId = 2;
const transformedColumns = columns.map(column => {
if (column.id === targetColumnId) {
return {
...column,
children: [
...column.children,
{ id, text }
]
}
}
return column;
});
setColumns(transformedColumns);
}
return (
<DndProvider backend={HTML5Backend}>
<div className='flex-container'>
{columns.map((column) => (
<Column
key={column.id}
title={column.title}
children={column.children}
onCardDropped={onCardDropped}
/>
))}
</div>
</DndProvider>
);
}
Any help is highly appreciated. Thanks.
You need to consider the previous state using the callback of the set state method. It starts to work after changing the onCardDropped as below.
const onCardDropped = ({ id, text }) => {
// let card = null;
const targetColumnId = 2;
setColumns((prevColumns) =>
prevColumns.map((column) => {
if (column.id === targetColumnId) {
return {
...column,
children: [...column.children, { id, text }]
};
}
return column;
})
);
};
It's always a good idea to use the state from the callback method as opposed to using the state object directly which might be stale.
Working Demo

SPFx Reactjs Cannot Read Properties of Undefined

I am using React to develop with SPfx for SharePoint online and am trying to rebuild a past solution I made, so that I can utilize the property pane of sharepoint to edit the webpart. I am getting an error of:
ERROR:
Cannot read properties of undefined (reading 'fileAbsoluteUrl')
when trying to render the grid. However, using the Edge developer tools, I can see the array being initialized with the information from the Property Pane.
If anybody could help explain why the code is returning an undefined, I would greatly appreciate it. Or if you have experience using PnP with SPFx.
This is my .tsx code:
export default class GridLayout extends React.Component<IGridLayoutProps, {}> {
public render(): React.ReactElement<IGridLayoutProps> {
const {
description,
isDarkTheme,
environmentMessage,
hasTeamsContext,
userDisplayName,
gridItems
} = this.props;
let arr = [];
arr.push(this.props.gridItems);
let firstItem = arr.slice(0, 1);
let firstRow = arr.slice(1, 6);
let items = arr.slice(6);
console.log(firstItem, firstRow, items);
if (this.props.gridItems && this.props.gridItems.length > 0) {
var gridFirstItem = firstItem.map(el => {
<div className={`${styles.tile1}`}>
<div key={el.Title} className={`${styles.galleryframe1}`} style={{ backgroundColor: '#2f2f2f' }}>
<a href={el.Hyperlink ? el.Hyperlink : '#'}>
<img className={`${styles.galleryimg}`} src={el && el.length > 0 ? el.filePicker.fileAbsoluteUrl : ''} />
</a>
</div>
</div>
});
console.log('first grid item good...')
var gridFirstRow = firstRow.map(el => {
<div className={`${styles.tile1}`}>
<div key={el.Title} className={`${styles.galleryframe1}`} style={{ backgroundColor: '#0069f8' }}>
<a href={el.Hyperlink ? el.Hyperlink : '#'}>
<img className={`${styles.galleryimg}`} src={el && el.length > 0 ? el.filePicker.fileAbsoluteUrl : ''} />
</a>
</div>
</div>
});
console.log('first row good...')
var restOfItems = items.map(el => {
<div className={`${styles.tile}`}>
<div key={el.Title} className={`${styles.galleryframe}`} style={{ backgroundColor: '#e64856' }}>
<a href={el.Hyperlink ? el.Hyperlink : '#'}>
<img className={`${styles.galleryimg}`} src={el && el.length > 0 ? el.filePicker.fileAbsoluteUrl : ''} />
</a>
</div>
</div>
});
console.log('grid good')
} else {
return (
<div className={`${styles.label}`}>Use Property Pane Editor to Add Tiles.</div>
)
}
return (
<body>
<div className={`${styles.footer}`} >Our Mission: Provide the fullest possible accounting for our missing personnel to their families and the nation.</div>
{/* Renders the grid */}
<div className={`${styles.grid}`}>
{gridFirstItem}
{gridFirstRow}
{restOfItems}
</div>
</body>
);
}
}
This is the code involving the property pane and the array:
PropertyFieldCollectionData("gridItems", {
key: "gridItemsFieldId",
label: "Grid Data",
panelHeader: "Grid Data Panel",
manageBtnLabel: "Manage grid data",
value: this.properties.gridItems,
fields: [
{
id: "Title",
title: "Item Title",
type: CustomCollectionFieldType.string,
required: true,
},
{
id: "Description",
title: "Item Description",
type: CustomCollectionFieldType.string,
},
{
id: "Hyperlink",
title: "Link to Open",
type: CustomCollectionFieldType.url,
required: true,
},
{
id: "filePicker",
title: "Select File",
type: CustomCollectionFieldType.custom,
onCustomRender: (
field,
value,
onUpdate,
item,
itemId,
onError
) => {
return React.createElement(FilePicker, {
context: this.context,
key: itemId,
buttonLabel: "Select File",
onChange: (filePickerResult: IFilePickerResult[]) => {
console.log('changing....', field);
onUpdate(field.id, filePickerResult[0]);
this.context.propertyPane.refresh();
this.render();
},
onSave: (filePickerResult: IFilePickerResult[]) => {
console.log('saving....', field);
if (filePickerResult && filePickerResult.length > 0) {
console.log('filePickerResult && filePickerResult.length > 0');
if (filePickerResult[0].fileAbsoluteUrl == null) {
console.log('filePickerResult[0].fileAbsoluteUrl == null');
filePickerResult[0].downloadFileContent().then(async r => {
let fileresult = await this.web.getFolderByServerRelativeUrl(`${this.context.pageContext.site.serverRelativeUrl}/SiteAssets/SitePages`).files.addUsingPath(filePickerResult[0].fileName, r, true);
filePickerResult[0].fileAbsoluteUrl = `${this.context.pageContext.site.absoluteUrl}/SiteAssets/SitePages/${fileresult.data.Name}`;
console.log('saving....', filePickerResult[0]);
onUpdate(field.id, filePickerResult[0]);
this.context.propertyPane.refresh();
this.render();
});
} else {
console.log('saving....', filePickerResult[0]);
onUpdate(field.id, filePickerResult[0]);
this.context.propertyPane.refresh();
this.render();
}
}
},
hideLocalUploadTab: false,
hideLocalMultipleUploadTab: true,
hideLinkUploadTab: false,
});
},
required: false,
},
],
disabled: false,
},

Hide an item dynamic depending by a condition

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

How to make some values default vaslues and enable and other no default and disbale in an admin dialog which activate contact form fields

I have a React app with an admin panel and one of the dialogs is for contact form fields.
Essentially in the contact form dialog there are switches which all by default are enable and those turn on the fields in the main app.
The screenshot shows the fields how are in the default way all turned on.
Right now the issue here is that the last field preferredContactWay should be off by default.
My solution to this problem was as follow but is a bad solution will be needed a more efficient one and less redundant code. A better solution is what I seek to my problem.
I created a second object called availableContactFields and removed from the original defaultContactFields preferredContactWay field.
const defaultContactFields = {
name: {
name: 'name',
type: 'name',
},
email: {
name: 'email',
type: 'email',
confirm: true,
},
phone: {
name: 'phone',
type: 'phone',
},
preferredContactHours: {
name: 'preferredContactHours',
type: 'select',
options: ['8-20', '8-12', '12-16', '16-20'],
defaultValue: '8-20',
},
};
const availableContactFields = {
name: {
name: 'name',
type: 'name',
},
email: {
name: 'email',
type: 'email',
confirm: true,
},
phone: {
name: 'phone',
type: 'phone',
},
preferredContactHours: {
name: 'preferredContactHours',
type: 'select',
options: ['8-20', '8-12', '12-16', '16-20'],
defaultValue: '8-20',
},
preferredContactWay: {
name: 'preferredContactWay',
type: 'select',
options: ['Phone call', 'SMS', 'Email'],
defaultValue: 'Phone call',
},
};
Then I have the next part function and I'm passing the default fields to have as default what should on and so the preferredContact way remains off initially
function EditContactFormDialog({ closeDialog, handleEdit, editItem }) {
const classes = useStyles();
const [disabled, setDisabled] = useState(false);
const [internalValue, setInternalValue] = useState(
get(editItem, 'value') || Object.values(defaultContactFields),
);
console.log({ internalValue });
function toggleEnabled(name, enabled) {
console.log(name, enabled);
const onlyEnabled = key =>
(internalValue.some(val => val.name === key) && key !== name) ||
(key === name && enabled);
const toConfig = key =>
internalValue.find(val => val.name === key) ||
availableContactFields[key];
setInternalValue(
supported
.map(itm => itm.name)
.filter(onlyEnabled)
.map(toConfig),
);
}
function toggleConfirm(name, confirm) {
setInternalValue(
internalValue.map(val => (val.name === name ? { ...val, confirm } : val)),
);
}
function toggleOptions(name, options) {
setInternalValue(
internalValue.map(val => (val.name === name ? { ...val, options } : val)),
);
}
the last part is the return of the component the map is taking this
const supported = [
{ name: 'name' },
{ name: 'email', confirmable: true },
{ name: 'phone', confirmable: true },
{ name: 'preferredContactHours' },
{ name: 'preferredContactWay' },
];
Then here the switches are rendered
return (
<Dialog open>
<DialogTitle className={classes.dialogTitle}>
Customize contact form fields
</DialogTitle>
<DialogContent>
<Table>
<TableBody>
{supported.map(({ name, confirmable }) => {
const value = internalValue.find(item => item.name === name);
console.log({ internalValue });
const isEnabled = Boolean(value);
console.log('boolean ->', { value }, ': ', { name }, ' : ', {
isEnabled,
});
const isConfirm = isEnabled && value.confirm;
const { options } = availableContactFields[name];
const valueOptions = get(value, 'options', []);
console.log(name, value);
return (
<TableRow key={name}>
<TableCell>
<FormControlLabel
control={
<Switch
color="primary"
checked={isEnabled}
onChange={({ target: { checked } }) =>
toggleEnabled(name, checked)
}
/>
}
label={name}
/>
</TableCell>
<TableCell>
{confirmable && (
<FormControlLabel
disabled={!isEnabled}
control={
<Checkbox
checked={isConfirm}
onChange={({ target: { checked } }) =>
toggleConfirm(name, checked)
}
/>
}
label="Confirm"
/>
)}
{options && (
<FormControl
disabled={!isEnabled}
className={classes.fullWidth}
>
<InputLabel>Options</InputLabel>
<Select
multiple
value={valueOptions}
onChange={({ target }) =>
toggleOptions(name, target.value)
}
renderValue={selected => selected.join(', ')}
>
{options.map(option => (
<MenuItem key={option} value={option}>
<Checkbox
checked={valueOptions.includes(option)}
/>
<ListItemText primary={option} />
</MenuItem>
))}
</Select>
</FormControl>
)}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</DialogContent>
<DialogActions style={{ justifyContent: 'space-between' }}>
<Button disabled={disabled} onClick={closeDialog}>
Cancel
</Button>
<Button
color="primary"
variant="contained"
disabled={disabled || internalValue.length < 1}
onClick={() => {
setDisabled(true);
handleEdit(internalValue);
}}
>
{disabled && <CircularProgress size={18} />}
Save
</Button>
</DialogActions>
</Dialog>
);
}
Answering my own question as I found a solution and could be ok at least doing the same as before.
To avoid having redundant code I added the following 2 lines inside the beginning of the component
const availableContactFields = { ...defaultContactFields };
delete availableContactFields.preferredContactWay;
const [internalValue, setInternalValue] = useState(
get(editItem, 'value') || Object.values(availableContactFields),
);
In this way I'm deleting from the default fields the preferredContactWay and then the switch is kept off in default session.
Not sure is the right solution but at this moment work

Resources