How to I fill a nested array with setState? - javascript

What this code is essentially supposed to do is concatenate an empty array object On Click with the add function. Then, I want to fill concatenate each sub array individually depending on the index of the sub Click. So each subList has its own Click to add elements to itself.
The problem is that I keep getting the wrong output when I use setState to update the inner subList. This doesn't happen when I mutate state directly, When I mutate state directly, I get correct results in the console window as intended.
import React, { Component } from 'react';
import './App.css';
//onClick: push an array object onto List
//On Sub Click: Fill the inner arrays individually
class AppTest extends Component {
state = {
List: [
{subList: []}
]
}
this function concatenates an array object each time it is clicked.
add = (event) => {
this.setState(
{List: this.state.List.concat({subList: []})}
);
}
this function grabs the current index of the List and ATTEMPTS to fill each subList individually
based on the index being clicked.
subadd = (i, event) => {
this.setState(
{List: [
{subList: this.state.List[i].subList.concat(0)}
]}
);
//When I mutate state directly, The entire code works as intended: Uncomment below
//to take a look
//this.state.List[i].subList = this.state.List[i].subList.concat(0);
//This is a nested loop that prints the contents of the entire array (including sublists) to the console
for(let i = 0; i < this.state.List.length; i++)
{
console.log(i + "a");
for(let j = 0; j < this.state.List[i].subList.length; j++)
{
console.log(j + "b");
}
}
}
render() {
return (
//My end game is to output input tabs for each element INCLUDING the subList elements in between the main
// List elements
<div>
{this.state.List.map(i => {
return(
<div>
<input value = "Init"/><br />
<div onClick = {this.subadd.bind(this, this.state.List.indexOf(i))}>subClick</div>
</div>
);
})}
<div onClick = {this.add}>Click</div>
</div>
);
}
}
export default AppTest;
/*
All inputs will output the same result: 0a, 0b, 1a, 2a, 3a ...
The length of how many elements are printed is simply the current length of the list.
*/

You can spread the array similar to how you do an Object, as arrays are objects in javascript.
Please find the code here - https://codesandbox.io/s/l4z5z47657
//spread array to new object with the Object keys starting from 0 corresponding to each element in array
let tempArray = { ...this.state.List };
//get the array that we need to change and concat to the sublist
let newSubList = tempArray[i];
newSubList.subList = newSubList.subList.concat(0);
//create new array and update the index with new value . Note the special syntax with [i].
//This is because we have spread an array and not an Object
let toChange = { ...tempArray, [i]: newSubList };
//COnvert the Object to array again
toChange = Object.values(toChange);
this.setState({ List: toChange });
console.log(this.state.List);
This will immutably update the state. You may be able to further reduce the number of lines, but use this as a start.

Related

How to add data to a predefined object if there is no data?

If the data is to be removed, I find the element with findIndex and discard it as a null value in isInArray. If there is no such data, how do I add it to the empty element starting from the first element? For example, if the element in the first data is full, it should know that the second element is empty and add it to it.
<template>
<input type="text" v-model="name">
<input type="text" v-model="surname">
<button #click="addCustomer"></button>
</template>
<script>
import Vue from "vue";
export default {
data(){
return{
name:null,
surname:null,
customers:[{},{},{}],
currentIndex:null
}
},
methods:{
addCustomer(){
let findIndex = this.customers.findIndex((customer) => customer.name === this.name );
let isInArray = findIndex !== -1;
if(isInArray){
Vue.set(this,"currentIndex",findIndex)
Vue.set(this.customers[this.currentIndex], 'name', null)
}else{
// What Should I write here?
}
}
}
}
</script>
Understanding from your question, If you want to add customer to list if not in customers by name and if exist then update the existing customer from the list.
Then your code should be like this.
<template>
<input type="text" v-model="name">
<input type="text" v-model="surname">
<button #click="addOrUpdateCustomer"></button>
</template>
<script>
import Vue from "vue";
export default {
data(){
return{
name:"",
surname:"",
customers:[]
}
},
methods:{
addOrUpdateCustomer() {
const customerAtIndex = this.customers.findIndex(c => c.name === this.name);
//existing customer
if(customerAtIndex > -1) {
// You can update anything here
let existingCustomer = this.customers[customerAtIndex];
existingCustomer.surname = this.surname;
this.customers[customerAtIndex] = existingCustomer;
}
else {
//new customer
this.customers.push({name: this.name, surname: this.surname});
}
}
}
}
</script>
I think there's a misconception here. customers is an array of empty objects, it could (or not) contain the information of your customers. But you don't necessarily need to create the empty object as placeholders. Array in JS have different methods you can use to achieve what you want. Like push method to add an element at the end of the array or slice to remove it.
In your example, it could be something like:
addCustomer(){
let findIndex = this.customers.findIndex((customer) => customer.name === this.name );
let isInArray = findIndex !== -1;
if (!isInArray){
this.customers.push("name", "Nacho")
}
}
If the customer is not in the array, it is supposed that you want to add it. If IT IS IN THE ARRAY... then it depends on your logic. But you can remove it using slice array method.
BTW, there's no need to use the Vue.set method, as the push method is the same for this case (Vue takes care of the new inserted value, so it is reactive).
As an user above states, I too believe you have a misconception going on.
An array is just a list of customers, the object just contains the attribute of a customer, if there's no customer in the list at the first place, there's no need to add an object.
A cart needs 5 fruits, if the cart is empty, I don't need to add empty strings on the cart.
If an array needs 5 numbers, but if the array is empty, there's no need to add 0s as a placeholder in the array
You are trying to do something like this
const cart = ["", "", "", "", ""]
const array = [0, 0, 0, 0, 0]
there's no reason to do this at all.
If you want to limit the size of the array, you can stop the function if the length of the array is at the limit.
For example
// stop the function if the array size is at 3
// means the size limit is 3
if (array.length === 3) return
const addCustomer = () => {
if (array.length === 3) return
array.push(value) // replace value with customer or whatever you want to add
}
An example with your implementation (Composition API)
Example in Options API

How to push item in specific index of and array of useState reactJs [duplicate]

I'm attempting to add an object at a specific point in my 'data' array which is this components state. The following isn't working, the array simply gets emptied.
addNewBulletAfterActive = () => {
const array = this.state.data;
const newBulletPoint = {
id: this.state.data.length += 1,
title: 'Click to add'
};
const newData = array.splice(this.state.activeBulletPointId, 0, newBulletPoint);
this.setState({
data: newData
});
}
The idea is that if I have a list of 10 bullet points, the user can click on the 4th bullet point and press enter to add a new bullet point directly after. I've not had any issues adding items to the end of the array but it looks like .splice is causing issues.
I believe this should do what you're after.
function addAfter(array, index, newItem) {
return [
...array.slice(0, index),
newItem,
...array.slice(index)
];
}
This function returns a new array with a new item inserted in the middle. It doesn't mutate your original array and so will play nicely with component's state and Redux.
You can then assign the output from this function to your state.
splice returns spliced items (which is empty since you splice 0 items) and mutates original array.
const newData = array.slice(0); // copy
newData.splice(this.state.activeBulletPointId, 0, newBulletPoint);
this.setState({
data: newData
});
I think this could be an easier and faster method to do this
/*Just plain JS*/
function AddAfter(array, newObject){
array.unshift(newObject);
}
/*In react If updating state*/
var _prev = this.state.your_array; //getting the current value for the state object
var newInfo = {id: 1, more: 'This is a new object'};
_prev.unshift(newInfo);

How to delete an element from array in react?

I have two functions , one of them adds an item in array and the other one delete from that array using React JS (hooks).[Both are handler of click event].
What I have works incorrectly.
``id`` comes from ``contact.length`` and I deleted it with``contacts.splice(id, 1)``.
I dont have any idea why it has this problem.
it doesnt delete what would be clicked but a random one.
function handleAddRecord(nameValue, phoneValue) {
setContacts([...contacts , {
id : contacts.length,
name : nameValue,
phone : phoneValue
}])
}
function handleDelete(id) {
console.log("manager", id);
const newContacts = contacts.splice([id],1);
setContacts([...newContacts]);
}
One of the issue on the implementation is id generation keeping it array length could lead to issue as you delete and add elements there could be scenarios where there is same id for multiple items.
One of most widely used generator is uuid https://www.npmjs.com/package/uuid
Usage
const uuid = require("uuid");
uuid.v4(); // ⇨ '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
Now use this in your implementation
Add Operation:
const handleAddRecord = (nameValue, phoneValue) => {
const newRecord = {
id: uuid.v4(), // This should be unique at all times
name: nameValue,
phone: phoneValue,
};
setContacts([...contacts, newRecord]);
};
Delete Operation:
Use filter rather than splice as for splice you'll need to find the index of the element with id. But with Filter it can be done is a single line
const handleDelete = (id) => {
setContacts(contacts.filter(item => item.id !== id));
};
Here we're assuming that id is the index of the element to be removed.
The splice function returns the removed elements, thus is not useful to take its result. Instead, make a copy of the array first, then remove the undesired element:
function handleDelete(id) {
console.log("manager", id);
const newContacts = [...contacts];
newContacts.splice(id,1);
setContacts(newContacts);
}
That's because splice alters the array itself.
More here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
Ok, id return index of current map?
Follow this example:
const assoc = [...contacts];
assoc.splice(id, 1);
setContacts(assoc);
You can delete the item by finding its index from array.
For Example:
function handleDelete(id) {
console.log("manager", id);
const index = contacts.findIndex((x) => x.id === id);
const newContacts = [
...contacts.splice(0, index),
...contacts.splice(index + 1),
];
setContacts(newContacts);
}
You need undestand, every time when i'll remove a item from a array of a index, that this index has use unique key... When React remove a item 6 (a example) this is remove of array first, and when react re-render function react can delete another component, because a array exist key 6, wehn you have more 6 item from array... Understand?
Follow a example:
import React, { useState } from 'react';
function User(data) { // data is a array
const [contacts, setContacts] = useState(data); // data return a list of contacts
/* contacts result a array object and has the following attributes
[{
name: 'Cael',
tel_number: '+55 11 9999-999',
e_mail: 'user#example.com',
! moment: "2021-06-15T05:09:42.475Z" // see this a date in ISO string
}]
*/
// about moment atribute:
// this atribute result when use `new Date().toISOString()`
// and this value is added in the moment that create a object in array list
// It's mean that every time result a unique key
const deleteFn = (val) => { // val result index of component and item array
const assoc = [...contacts];
assoc.splice(val, 1);
setContacts(assoc);
}
return (
<div>
{!!contacts.length &&
contacts.map((assoc, i) => { // variable i result in your id
const { moment, name, e_mail, tel_number } = assoc; // moment use a unique key
return (
<li key={moment}>
<span>{name}</span>
<span>{e_mail}</span>
<span>{tel_number}</span>
<button type="button" onClick={() => deleteFn(i)}>Delete</button>
</li>
);
})}
</div>
);
}
export default User;
I hope, this helpfull you!

How to add an item in an array FROM a specific index-Javascript

I'm looking to insert an element from index 1 of an array:
render() {
if (some condition) {
let rules = [{label:"a"},{label:"b"},{label:"c"},{label:"d"}];
let data = [];
rules.forEach((rule) => {
data.push(
<div>
{//some condition to true &&
<label>OR</label>} //this OR should be added only from the 1st index of the array
<ComboBox
placeholder= "Select a token ..."
value={someval}
listItems={list} />
</div>)
});
return data;
}
}
The above will be pushed for all "true" conditions, but I need to push another element only from beginning of index 1 of an array. In short only the first element in the array should have this element. Something like:
if (some condition)
{
data.push(
<span>Test</span><div><span>A</span></div>
)
}
At the end, should look something like: https://jsfiddle.net/w42hke3h/
How can do this? Any ideas? Thanks!
use unshift. It works exactly like push but adds the element to the front of the array.
let rules = [{label:"a"},{label:"b"},{label:"c"},{label:"d"}];
let data = [];
rules.forEach(() => {
if (some condition) {
data.unshift(...);
}
)};

Is it possible to map only a portion of an array? (Array.map())

I am building a project using React.js as a front-end framework. On one particular page I am displaying a full data set to the user. I have an Array which contains this full data set. It is an array of JSON objects. In terms of presenting this data to the user, I currently have it displaying the whole data set by returning each item of data using Array.map().
This is a step in the right direction, but now I need to display only a portion of the data-set, not the whole thing, I also want some control in terms of knowing how much of the total data set has been displayed, and how much of the data set is yet to be displayed. Basically I am building something like a "view more" button that loads more items of data to the user.
Here is what I am using now where 'feed' represents my Array of JSON objects. (this displays the whole data set.)
return (
<div className={feedClass}>
{
feed.map((item, index) => {
return <FeedItem key={index} data={item}/>
})
}
</div>
);
I am wondering if it is possible to use .map() on only a portion of the array without having to break up the array before hand? I know that a possible solution would be to hold the full data set, and break it off into portions, and then .map() those portions, but is there a way to .map() a portion of the array without having to break it up?
Any and all feedback is appreciated. Thanks!
Do not try to solve this problem with a hack in your mapping step.
Instead, slice() the list to the right length first before the mapping:
class Feed extends React.Component {
constructor(props) {
super(props)
this.handleShowMore = this.handleShowMore.bind(this)
this.state = {
items: ['Item A', 'Item B', 'Item C', 'Item D'],
showItems: 2
}
}
handleShowMore() {
this.setState({
showItems:
this.state.showItems >= this.state.items.length ?
this.state.showItems : this.state.showItems + 1
})
}
render() {
const items = this.state.items.slice(0, this.state.showItems).map(
(item) => <div>{item}</div>
)
return (
<div>
{items}
<button onClick={this.handleShowMore}>
Show more!
</button>
</div>
)
}
}
ReactDOM.render(
<Feed />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='root'></div>
The easiest way in my head is just to use a filter and map
const feed = [1,2,3,4,5,6,7,8,9,10,11]
feed.filter((item, index) => index < 5).map((filteredItem) => //do somthing with filtred item here//)
where 5 is just a number of items you want to get
you could use the slice function before to map the array, it looks like you want to do some pagination there.
var fruits = ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango'];
var citrus = fruits.slice(1, 3);
// fruits contains ['Banana', 'Orange', 'Lemon', 'Apple', 'Mango']
// citrus contains ['Orange','Lemon']
Array.reduce should do what you're asking for. Just change the if statement depending on which range you want.
var excludeAfterIndex = 5;
feed.reduce((mappedArray, item, index) => {
if (index > excludeAfterIndex) { // Whatever range condition you want
mappedArray.push(<FeedItem key={index} data={item}/>);
}
return mappedArray;
}, []);
If you just want to map a portion of an array, you should first filter() your array to obtain the expected portion according to conditions :
array.filter(item => <condition>).map();
Yes, you can map portion of array, based on index. For example:
yourArray = yourArray.map(function (element, index, array) {
if (array.indexOf(element) < yourIndex) {
return {
//logic here
};
} else {
return {
//logic here
};
}
});
You can use slice to get portion of an array:
const data = [1,2,3,4,5,6,7,8,9,10,11]
var updatedData = data.slice(0, 3);
Array#map iterates over all items.
The map() method creates a new array with the results of calling a provided function on every element in this array.
You could use Array#filter
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
for the wanted items and then apply map for the wanted format.
There is no version of the map() function that only maps a partial of the array.
You could use .map() in conjunction with .filter().
You get the index of the current element as the second arg of map and if you have a variable for current page and page size you can quite easily filter the right page from your array without having to really slice it up.
var currentPage = 1;
var pageSize = 25;
dataArray.filter(function(elt, index) {
var upperThreshold = currentPage * pageSize;
var lowerThreshold = currentPage * pageSize - pageSize;
return index < upperThreshold && index > lowerThreshold;
});
Using slice() is better than adding a condition to your map or reduce function, but it still creates an additional, unused copy of that segment of the array. Depending on what you're doing, that might not be desired. Instead, just use a custom map function:
function sliceMap(fn, from, toExclusive, array) {
const len = toExclusive - from;
const mapped = Array(len);
for (let i = 0; i < len; i++) {
mapped[i] = fn(array[i + from], i);
}
return mapped;
};
Note that fn receives the array value and the (now) zero-based index. You might want to pass the original index (i + from). You might also want to pass the full array as a third parameter, which is what Array.map does.
Use this, easy approach
const [limit, setLimit] = useState(false);
const data = [{name: "john}, {name: 'Anna'}]
Here we will have 2 cases:
Display only first data which is John
Display all
data.slice(0, extended ? data.length : 1).map((item, index) => <Text>{item.name}</Text>)
....

Categories