Deleting and copying array elements in React - javascript

I have been tasked to add a copy and delete button on one of our tables. I am trying to pass the index from the map to the delete and copy onclick and this does delete but...
Problem: If you copy a row it will have the exact same "i" as the original, and it moves the position of all the ones below it messing up the delete tremendously
I was under the impression that if I setRows() to something new it would run the mapping again and give them all the correct i's in each function but this doesn't seem to be the case, why?
const AdvancedTable = () => {
const [rows, setRows] = useState(tableRows); ///tableRows is basically an array of divs
const deleteOnClick = (i: number) => {
setRows(() => {
const myRows = [...rows];
myRows.splice(i, 1);
return myRows;
});
}
const copyOnClick = (i: number) => {
setRows(() => {
const myRows = [...rows];
myRows.splice(i, 0, rows[i]);
return myRows;
});
}
return (
<Paper>
{
rows.map((row: any, i: number) => (
<div>
<IconButton onClick={() => { deleteOnClick(i) }} size="small">
<ClearIcon />
</IconButton>
<IconButton onClick={() => { copyOnClick(i) }} size="small">
<FileCopyIcon />
</IconButton>
</div>
</TableCell>
{row}
))}
</Paper>
);
}
export default AdvancedTable;

Do you want the copy method to copy the row below the one you clicked or to send it to then end of the array?
maybe this can help you assuming you can call the i value from your row object with .i
const copyOnClick = (i: number) => {
setRows(() => {
const myRows = [...rows];
// send the new row to the end of the array
myRows.splice(rows.length, 0, rows[i]);
// change the new row i value to + 1 of the last object that was previously in the array
myRows[myRows.pop().i].i = rows.pop().i + 1
return myRows;
});
}

Related

I need to open and close accordion based on arrow click

I am using Material UI accordion my issue is if I click on the arrow accordion will get open but again I click on the arrow it will not get closed I need to set it when the user clicks on the arrow according will close and open based on the arrow click check code sandbox link for better understanding.
export default function ControlledAccordions() {
const [expanded, setExpanded] = React.useState(false);
// const handleChange = (panel) => (event, isExpanded) => {
// setExpanded(isExpanded ? panel : false);
// };
const handleChange = (pannel) => {
setExpanded(pannel);
};
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
return (
<Accordion expanded={expanded === `panel${i}`}>
<AccordionSummary
expandIcon={
<ExpandMoreIcon
onClick={() => {
handleChange(`panel${i}`);
}}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
fdsfdsf
</AccordionSummary>
<AccordionDetails>dfdf</AccordionDetails>
</Accordion>
);
})}
</div>
);
}
Code SandBox Link
you need to reset panel in that case. You can do that in change handler.
const handleChange = (pannel) => {
setExpanded(expended === pannel ? '' : pannel);
};
when you click the already expanded panel, it just sets it to be expanded again.
you need to check whether the clicked panel is already expanded and if so collapse it instead of expanding it:
const handleChange = (pannel) => {
if (expanded === pannel) setExpanded(false);
else setExpanded(pannel);
};
Create another component called MyAccordian and keep toggling accordion logic in that component. That way you don't need to handle toggling for each and every component separately.
export default function ControlledAccordions() {
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
return <MyAccordian value={value} />;
})}
</div>
);
}
const MyAccordian = ({ value }) => {
const [expanded, setExpanded] = React.useState(false);
return (
<Accordion expanded={expanded}>
<AccordionSummary
expandIcon={
<ExpandMoreIcon
onClick={() => {
setExpanded((prev) => !prev);
}}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
{value}
</AccordionSummary>
<AccordionDetails>{value}</AccordionDetails>
</Accordion>
);
};
Working Demo
export default function ControlledAccordions() {
// initial state, everything is closed,
const [expandedIndex, setExpandedIndex] = React.useState(-1);
// this should be handleClic
const handleChange = (index) => {
// in useState, current expandedIndex is passed as the argument
// whatever we return will be set as the expandedIndex
setExpandedIndex((currentIndex) => {
// if any box is open, currentIndex will be that index
// when I click on the open box, it will set the expandedIndex=-1
if (currentIndex === index) {
return -1;
} else {
// If I reached here, that means I am on a closed box
// when I click I swithc the expandedIndex to current box's index
return index;
}
});
};
const panaalData = ["panel1", "panel2", "panel3", "panel4"];
return (
<div>
{panaalData.map((value, i) => {
// when handleChange runs on AccordionSummary expandedIndex===i
// that means when i click on the current box, it will be open
const isExpanded = expandedIndex === i;
return (
<Accordion expanded={isExpanded}>
<AccordionSummary
onClick={() => handleChange(i)}
expandIcon={
// I dont know #mui/material too much.
// main question is "I need to open and close accordion based on arrow click"
<ExpandMoreIcon
onClick={() => handleChange(i)}
style={{ cursor: "pointer" }}
/>
}
aria-controls="panel1d-content"
id="panel1d-header"
>
{value}
</AccordionSummary>
<AccordionDetails
style={{ backgroundColor: "green" }}
>{`box index ${i} is open`}</AccordionDetails>
</Accordion>
);
})}
</div>
);
}
proof of work:
const handleChange = (pannel) => {
setExpanded(!pannel);
};

how this specific code removes item from list

I'm trying to learn to react online and I understood everything except this line code
const removeItem = (id) => {
let newPeople = people.filter((person) => person.id !== id);
setPeople(newPeople);
};
especially how person.id !== idremoves the item from list and add to new list
here is the full code
import React from 'react';
import { data } from '../../../data';
const UseStateArray = () => {
const [people, setPeople] = React.useState(data);
const removeItem = (id) => {
let newPeople = people.filter((person) => person.id !== id);
setPeople(newPeople);
};
return (
<>
{people.map((person) => {
const { id, name } = person;
return (
<div key={id} className='item'>
<h4>{name}</h4>
<button onClick={() => removeItem(id)}>remove</button>
</div>
);
})}
<button className='btn' onClick={() => setPeople([])}>
clear items
</button>
</>
);
};
export default UseStateArray;
first you shold khow how filter works,
The filter() method creates a new array filled with elements that pass a test provided by a function.
in your case test is person.id !== id,
if test passed for an element that element will be in new array.
otherwise element will not be in new array. is it clear?
The filter method creates a shallow copy of an array but not the whole array but only those elements that fulfills the predicate.
So newPeople will contain a copy of all the elements within people that it's people[element].id is different than id.
Visit https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter for additional details of filter method.

Is it Possible to Dynamically Generate Dropdown Items?

I'm working in react, and for one part of the project I need x amount of dropdown menus. Each dropdown menu will have y amount of options to choose from.
Usually with things like this, I can just use a simple map function and I am good to go, however the syntax gets a little tricky since one DropdownMenu has many Dropdown.Items. My code looks like this, no errors pop up and the console.log statements return exactly what is to be expected, but absolutely nothing renders.
const renderDefaultScheduling = () => {
return allDevices.map( (device, superIndex) => {
<Card>
{renderAllOfThisDevice(device, superIndex)}
</Card>
})
}
const renderAllOfThisDevice = (deviceObj, superIndex) => {
let oneDeviceDropdowns = []
console.log(deviceObj)
for (let i = 1; i <= deviceObj.amount; i = i + 1){
let thisOptions = renderDropDownOptionsFromList(deviceObj, superIndex)
oneDeviceDropdowns.push(
<DropdownButton title={deviceObj[i]}>
{thisOptions}
</DropdownButton>
)
}
return(
<Card>
<Card.Title>{deviceObj.name}</Card.Title>
<Card.Body>
{oneDeviceDropdowns}
</Card.Body>
</Card>
)
}
const renderDropDownOptionsFromList = (deviceObj, superIndex) => {
console.log(deviceObj)
return deviceObj.remaining_drivers.map( (driver, index) => {
<Dropdown.Item key={index} onClick={() => handleDriverSelection(deviceObj, driver, index, superIndex)}>
{driver.firstname} {driver.lastname}
</Dropdown.Item>
})
}
What gets me, is not even the <Card.Title>{deviceObj.name}</Card.Title> line renders, which is not inside the nested map, only the first layer of it... So if deviceObj is logging properly, I see legitimately no reason why that line wouldn't be rendering. Does anyone have any ideas, am I evenm able to do this with DropDown Menus?
no data is showing beacuse you are not returning it from the map function callback in renderDefaultScheduling and renderDropDownOptionsFromList
i have marked the return statement with return
const renderDefaultScheduling = () => {
return allDevices.map( (device, superIndex) => {
****return**** <Card>
{renderAllOfThisDevice(device, superIndex)}
</Card>
})
}
const renderAllOfThisDevice = (deviceObj, superIndex) => {
let oneDeviceDropdowns = []
console.log(deviceObj)
for (let i = 1; i <= deviceObj.amount; i = i + 1){
let thisOptions = renderDropDownOptionsFromList(deviceObj, superIndex)
oneDeviceDropdowns.push(
<DropdownButton title={deviceObj[i]}>
{thisOptions}
</DropdownButton>
)
}
return(
<Card>
<Card.Title>{deviceObj.name}</Card.Title>
<Card.Body>
{oneDeviceDropdowns}
</Card.Body>
</Card>
)
}
const renderDropDownOptionsFromList = (deviceObj, superIndex) => {
console.log(deviceObj)
return deviceObj.remaining_drivers.map( (driver, index) => {
****return**** <Dropdown.Item key={index} onClick={() => handleDriverSelection(deviceObj, driver, index, superIndex)}>
{driver.firstname} {driver.lastname}
</Dropdown.Item>
})
}

Render a promised value for each item in a mapped list

I am trying to count the number of items in each cache, and render the results in a list. I can console.log() the result, but can't render it. In this cache 'tiles' there are 5 items. I've reviewed all the related questions but they all show how to console.log() a value from a promise which I can do, but I can't figure out how to render the value for each item in an array.
These are the components:
// Array passed to Layers.js
const offlineLayers = [
{
title: "Demo Activity",
url: "tiles/{z}/{x}/{y}.png",
cacheName: "tiles",
cacheLength: tiles.length // 2141 items
}
];
// Layers.js - where the details about each object in ```offlineLayers``` are rendered
const getCache = async (cacheName) => {
const cache = await (await caches.open(cacheName)).keys();
return await cache;
};
{offlineLayers && (
<FormControl className={classes.formControl}>
<FormLabel>Offline Layers</FormLabel>
<RadioGroup value={baseValue} onChange={handleBaseChange}>
{offlineLayers.map((layer, i) => {
let length;
getCache(layer.cacheName).then((cache) => {
console.log(cache.length); // 5 which is the correct number
return (length = cache.length);
});
return (
<Fragment>
<FormControlLabel
className={classes.formControlLabel}
key={i}
value={layer.title}
control={<Radio size="small" />}
label={layer.title}
/>
<FormHelperText
className={classes.offlineCount}
>{`Downloaded: ${length} / ${layer.cacheLength}`}</FormHelperText>
</Fragment>
);
})}
</RadioGroup>
</FormControl>
)}
I need length to render the number of items in the cache, for each object in offlineLayers. It console.log()s the correct number (5) but currently renders undefined.
Thanks for any suggestions on how to achieve this.
If anyone has a similar problem, I solved it by appending the number of downloaded tiles to the original array before mapping it, which makes more sense than my previous plan to get the number of tiles for each layer during the map itself.
const getCacheLength = () => {
offlineLayers.map(function (obj) {
caches.open(obj.cacheName).then(function (cache) {
cache.keys().then(function (keys) {
return Object.assign(obj, {
downloadedLength: keys.length
});
});
});
});
return offlineLayers;
};
const offlineLayersWithCache = offlineLayers && getCacheLength();
// then map the new array offlineLayersWithCache as normal

Update the array values according to user input

I try to implement an component in my application. There i want to set the numbers of rendered paragraphs according to the numbers set by the user. For example if user selects 3 should appear 3 paragraphs, but if user after that select 1, the number of paragraphs should decrease to 1. This is my code:
const Nr = () => {
const [nr, setNr] = useState([]);
function onChange(value) {
console.log("changed", value);
setNr([...nr, value]);
}
return (
<div>
{nr.map(i => {
return <p>{i}</p>;
})}
<InputNumber min={1} max={10} onChange={onChange} />;
</div>
);
};
export default Nr;
But now if select 3 the paragraphs are genereted ok, but if after that i select 2, the paragraphs does not decrease, but increase. How to solve this?
demo: https://codesandbox.io/s/cranky-glitter-9d7sn?file=/Number.js:163-490
A good approach is to generate a new array using the incoming value (from the onChange function) as the array length.
Codesandbox: https://codesandbox.io/s/proud-http-r49px?file=/Number.js:163-526
const Nr = () => {
const [nr, setNr] = useState([]);
function handleInputChange(value) {
const newArray = Array.from({ length: value }, (_, index) => index + 1);
setNr(newArray);
}
return (
<div>
{nr.map(i => (
<p key={i}>{i}</p>
))}
<InputNumber min={1} max={10} onChange={handleInputChange} />;
</div>
);
};

Categories