React Nested Array - arrays

I need to be able to combine this into one function. There are 2 separate arrays...
{this.state.telephoneType.map((telephoneType, ttidx) => ())}
and...
{this.state.telephone.map((telephone, tidx) => ())}
Basically, this is because I have a button which concatenates the 2 functions and it has to be outside the row class (MDBRow) so the UI doesn't break.
<MDBRow className="grey-text no-gutters my-2">
{this.state.telephoneType.map((telephoneType, ttidx) => (
<MDBCol md="4" className="mr-2">
<select
key={ttidx}
defaultValue={telephoneType.name}
onChange={this.handleTelephoneTypeChange(ttidx)}
className="browser-default custom-select">
<option value="Mobile">Mobile</option>
<option value="Landline">Landline</option>
<option value="Work">Work</option>
</select>
</MDBCol>
))}
{this.state.telephone.map((telephone, tidx) => (
<MDBCol md="7" className="d-flex align-items-center">
<input
value={telephone.name}
onChange={this.handleTelephoneChange(tidx)}
placeholder={`Telephone No. #${tidx + 1}`}
className="form-control"
/>
<MDBIcon icon="minus-circle"
className="mr-0 ml-2 red-text"
onClick={this.handleRemoveTelephone(tidx)} />
</MDBCol>
))}
</MDBRow>
<div className="btn-add" onClick={this.handleAddTelephone}>
<MDBIcon className="mr-1" icon="plus-square" />
Add Telephone
</div>
This is the handleAddTelephone function...
handleAddTelephone = () => {
this.setState({
telephone: this.state.telephone.concat([{ name: "" }]),
telephoneType: this.state.telephoneType.concat([{ name: "" }])
});
};
and the Constructor looks like this...
class InstallerAdd extends React.Component {
constructor() {
super();
this.state = {
role: "Installer",
name: "",
telephoneType: [{ name: "" }],
telephone: [{ name: "" }],
tidx: "",
emailType: [{ email: "" }],
email: [{ email: "" }],
eidx: "",
notes: ""
};
}
}
Can I nest one array inside the other? I'm not sure how to do this so any advice appreciated. Thanks.
Edit:
These are the 2 telephone functions which need to be 1 function...
I have updated with new nested array for each
handleTelephoneChange = tidx => evt => {
const newTelephone = this.state.telephone.type.map((telephone, tsidx) => {
if (tidx !== tsidx) return telephone;
return { ...telephone, name: evt.target.value };
});
this.setState({ telephone: newTelephone }, () => {
// get state on callback
console.log(this.state.telephone.number[tidx].name)
}
);
};
handleTelephoneTypeChange = ttidx => evt => {
const newTelephoneType = this.state.telephone.number.map((telephoneType, ttsidx) => {
if (ttidx !== ttsidx) return telephoneType;
return { ...telephoneType, name: evt.target.value };
});
this.setState({ telephoneType: newTelephoneType }, () => {
// get state on callback
console.log(this.state.telephone.type[ttidx].name)
}
);
};
My constructor now looks like this...
class InstallerAdd extends React.Component {
constructor() {
super();
this.state = {
role: "Installer",
name: "",
telephone: {
type: [{ name: "" }],
number: [{ name: "" }]
},
tidx: "",
emailType: [{ email: "" }],
email: [{ email: "" }],
eidx: "",
notes: ""
};
}

Although I still don't quite understand how your UI "breaks" (video won't load for me), I hope I can help.
Basically the short answer about trying to map two arrays singly is that you can't (well, shouldn't), but with some assumptions about the two array's length always being equal and the order is the same between the two, then you can map over the first array and use the passed index from the map function to access the second.
arrayA.map((itemFromA, index) => {
// do stuff with item from A
const itemFromB = arrayB[index];
// do stuff with item from B
});
I think the better solution is to keep only a single array in state to map over in the first place.
state = {
telephones: [], // { type: string, number: string }[]
}
...
state.telephones.map(({ type, number }, index) => {
// do stuff with telephone type
...
// do stuff with telephone number
});
If you also really want only a single change handler (I recommend they be separate), you can adopt a redux action/reducer type handler that accepts an object as a parameter that has all the data you need to update an array entry (index, update type, value). Here's a pretty clean example, but requires a bit of "action" setup when called:
changeHandler = (index, type) => e => {
const newTelephoneData = [...this.state.telephones];
newTelephoneData[index][type] = e.target.value;
this.setState({ telephones: newTelephoneData });
}
<select
value={type}
onChange={this.telephoneChangeHandler(index, "type")}
>
// options
</select>
...
<input
value={number}
onChange={this.telephoneChangeHandler(index, "number")}
placeholder={`Telephone No. #${index + 1}`}
/>
Below I've created a few working sandbox demos:
Dual telephone data arrays, single change ("action/reducer") handler, separate add/remove functions, single map pass using index accessor to second array:
Single telephone data array, single change handler, separate add/remove, single map pass:
Use react useReducer, single array, reducer handles add/remove/update, single map pass:
I've included code documentation but if anything is unclear please let me know and I can clean up these demos.

Yes, absolutely, something along:
telephone: {
list: [1,2,3],
type: [“foo”, “bar”, “baz”]
}

Related

Update object from input without setState

I'm new to React JS (and JS, in general). Now I'm trying to code a simple task tracker.
So, I have all tasks in state element of MyTodoList class. There I draw each task separately with Task constant.
I want to implement adding a new task with 2 inputs: name and description.
I do it in MyTodoList with creating a new object (newTask), so that I can add it to state list later. However, I guess that I'm writing onChange method for input incorrectly. newTask seems to be updating inside the function (logged it in console), but it does not change outside (in input space there are no changes with typing). Obviously I cannot use setState as I want to update a non-state object (object is mutable, so I do not understand why it won't change).
I'm not sure whether I'm updating the object wrongly or whether my whole concept of adding new task is wrong. Would be grateful if you could explain me my mistakes.
Here's the code:
const TaskAdd = ({value, onChange, placeholder, name}) => {
return (
<input value={value} onChange={onChange} placeholder={placeholder} name={name}/>
)
}
const Task = ({id, name, description, completed}) => {
const handleClick = () => {
}
return (
<div className='task'>
<h3>{name}</h3>
<div>{description}</div>
<div>{completed}</div>
<button onClick={handleClick} className='button1'>CLICK</button>
</div>
)
}
class MyTodoList extends React.Component {
state = {
tasks: [
{
id: 1,
name: 'Walk the dog',
description: 'Have to walk the dog today',
completed: false,
},
]
}
maxId = this.state.tasks[this.state.tasks.length - 1].id;
newTask = {
id: this.maxId,
name: '',
description: '',
completed: false,
}
handleChange = (event) => {
const {value, name} = event.currentTarget
this.newTask[name] = this.newTask[name] + value
}
render () {
return(
<div>
<header><h1>TO-DO</h1></header>
<div className='addTask'>
<h2>Let's add something new</h2>
<TaskAdd value={this.newTask.name} onChange={this.handleChange}
placeholder='Name' name='name'/>
<TaskAdd value={this.newTask.description} onChange={this.handleChange}
placeholder='Description' name='description'/>
<p> {this.newTask.name}</p>
<button className='button1'><h3>Add</h3></button>
</div>
<div>{this.state.tasks.map(task => <Task id={task.id} name={task.name}
description={task.description} completed={task.completed}/>)}
</div>
</div>
)
}
}
const App = () => {
return (
<MyTodoList />
)
}
export default App;
Obviously I cannot use setState as I want to update a non-state object
If you want the screen to update you have to use state. The setState function is the only* way to tell react that something change and it needs to rerender.
So, expand your state to have new task in it:
state = {
tasks: [
{
id: 1,
name: 'Walk the dog',
description: 'Have to walk the dog today',
completed: false,
},
]
newTask: {
id: 2,
name: '',
description: '',
completed: false,
}
}
With that you'll need to update your render function to access it in state, as in:
<TaskAdd
value={this.state.newTask.name}
onChange={this.handleChange}
placeholder='Name'
name='name'
/>
And then when you set state, make a copy instead of mutating:
handleChange = (event) => {
const {value, name} = event.currentTarget
this.setState({
newTask: {
...this.state.newTask,
[name]: this.state.newTask[name] + value
}
});
}
Your code didn't include an implementation for the add button, but when you do, you'll probably take this.state.newTask and add it to the end of this.state.tasks (you'll make a copy of the array, not mutate it), and then create a new object to replace this.state.newTask
*ok, technically there's forceUpdate, but don't use that.

In ReactJS how do I keep track of the state of a group of checkboxes?

I am trying to keep track of which boxes are checked in my local state(you can check multiple boxes). I want to be able to check and uncheck the boxes and keep track of the ids of the boxes that are checked. I will do something with the values later. This is what I have so far:
import React, { Component } from 'react'
import './App.css'
import CheckBox from './CheckBox'
class App extends Component {
constructor(props) {
super(props)
this.state = {
fruits: [
{id: 1, value: "banana", isChecked: false},
{id: 2, value: "apple", isChecked: false},
{id: 3, value: "mango", isChecked: false},
{id: 4, value: "grape", isChecked: false}
],
fruitIds: []
}
}
handleCheckChildElement = (e) => {
const index = this.state.fruits.findIndex((fruit) => fruit.value === e.target.value),
fruits = [...this.state.fruits],
checkedOrNot = e.target.checked === true ? true : false;
fruits[index] = {id: fruits[index].id, value: fruits[index].value, isChecked: checkedOrNot};
this.setState({fruits});
this.updateCheckedIds(e);
}
updateCheckedIds = (e) => {
const fruitIds = [...this.state.fruitIds],
updatedFruitIds= fruitIds.concat(e.target.id);
this.setState({updatedFruitIds});
}
render() {
const { fruits } = this.state;
if (!fruits) return;
const fruitOptions = fruits.map((fruit, index) => {
return (
<CheckBox key={index}
handleCheckChildElement={this.handleCheckChildElement}
isChecked={fruit.isChecked}
id={fruit.id}
value={fruit.value}
/>
);
})
return (
<div className="App">
<h1>Choose one or more fruits</h1>
<ul>
{ fruitOptions }
</ul>
</div>
);
}
}
export default App
So basically I am able to check and uncheck the boxes, but I cannot seem to update and store the fruitIds. Here is my checkbox component also:
import React from 'react'
export const CheckBox = props => {
return (
<li>
<input key={props.id}
onChange={props.handleCheckChildElement}
type="checkbox"
id={props.id}
checked={props.isChecked}
value={props.value}
/>
{props.value}
</li>
)
}
export default CheckBox
Also if you have a cleaner ways to do this than the way I am doing it, I would love to see it.
This is what if I were to approach it I will do. I will create a one dimensional array that holds the id's of the fruits when A fruit if clicked(checked) I will add it id to the array and when its clicked the second time I check if the array already has the id I remove it. then the presence of id in the array will mean the fruit is checked otherwise its not checked So I will do something like below
this.state={
fruitsIds: []
}
handleCheckChildElement=(id) => {
//the logic here is to remove the id if its already exist else add it. and set it back to state
const fruitsIds = this.state.fruitsIds;
this.setState({fruitsIds: fruitsIds.contains(id) ? fruitsIds.filter(i => i != id) : [...fruitsIds, id] })
}
then I render the checkboxes like
<CheckBox key={index}
handleCheckChildElement={this.handleCheckChildElement}
isChecked = { this.state.fruitsIds.contains(fruit.id)}
id={fruit.id}
/>
This is because you can always use the id to get all the other properties of the fruit so there is absolutely no need storing them again.
then the checkbox component should be as follows
export const CheckBox = props => {
return (
<li>
<input key={props.id}
onChange={() => props.handleCheckChildElement(props.id)}
type="checkbox"
id={props.id}
checked={props.isChecked}
value={props.value}
/>
{props.value}
</li>
)
}
The reason you are not getting your ids updated because:
You are trying to concat a non array element to an array.
concat is used for joining two or more arrays.
updatedFruitIds = fruitIds.concat(e.target.id);
You are not updating your actual fruitIds state field. I dont know why you are using "updatedFruitIds" this variable but due to above error it will always result into a single element array.
this.setState({ updatedFruitIds });
updateCheckedIds = e => {
const fruitIds = [...this.state.fruitIds],
updatedFruitIds = fruitIds.concat([e.target.id]);
this.setState({ fruitIds: updatedFruitIds });
};
OR
updateCheckedIds = e => {
const fruitIds = [...this.state.fruitIds, e.target.id],
this.setState({ fruitIds });
};

On an onClick method, I can't use a .push twice

I'm trying to go through a list of employees and when I click a button it adds their employee ID in an array. I try to do this with a .push method onto the array, but it only works once, and then when I try to push the button for the next employee it says .push is not a function.
constructor(props:any){
super(props);
this.state={
employees:[{name: 'joe', id: 12345},{name: 'kelly', id: 12321},{name: 'Jessica', id: 12255},{name: 'Paul', id: 98798}],
ids:[],
badgeID: '',
currentEmployee: null
}
}
check = () => {
const {ids, badgeID, employees} = this.state
let filteredEmployee = employees.filter(employee => {
return employee.id === Number(badgeID)
})
this.setState({
currentEmployee: filteredEmployee,
drinkModal: true,
ids: ids.push(badgeID)
})
console.log('ids', this.state.ids)
}
handleChange = (e: any, type: string) => {
if(type === 'badge'){
this.setState({badgeID: e.target.value})
}
}
render(){
return(
<TextField onChange={e => this.handleChange(e, 'badge')}/>
<Button onClick={this.check}>Add Employee</Button>
)}
You are doing:
ids: ids.push(badgeID)
This saves the return value of the push method which is not an array, so the next time you call push it fails. Instead you need to do something like:
ids: [...ids, badgeID]
or:
ids: ids.concat([badgeId])
This is because ids.push returns the length of the new array:
check the docs
Basically you are setting to ids an integer value.

SetState of an array of Objects in React

Ok, so I'm so frustrated finding the right solution so I'm posting the problem here. Giving an answer would help me a lot, coz I'm stuck!
the state tree looks like this
this.state = {
itemList : [{
_id : 1234,
description : 'This the description',
amount : 100
}, {
_id : 1234,
description : 'This the description',
amount : 100
}],
}
The problems are :
can not update any specific key in the Object of the array according
to the _id
The previous state should remain intact
answered March 25 2018
This is how you would use setState and prevstate to update a certain attribute of an object in your data structure.
this.setState(prevState => ({
itemList: prevState.itemList.map(
obj => (obj._id === 1234 ? Object.assign(obj, { description: "New Description" }) : obj)
)
}));
answered Dec 12 2019 (REACT HOOKS)
import React, { useState } from 'react';
const App = () => {
const [data, setData] = useState([
{
username: '141451',
password: 'password',
favoriteFood: 'pizza',
},
{
username: '15151',
password: '91jf7jn38f8jn3',
favoriteFood: 'beans'
}
]);
return (
<div>
{data.map(user => {
return (
<div onClick={() => {
setData([...data].map(object => {
if(object.username === user.username) {
return {
...object,
favoriteFood: 'Potatos',
someNewRandomAttribute: 'X'
}
}
else return object;
}))
}}>
{JSON.stringify(user) + '\n'}
</div>
)
})}
</div>
)
}
to update state constructed like this you will have to find index of element you want to update, copy the array and change found index.
it's easier and more readable if you keep list of records as object, with id as a key and record as a value.
The only way to do this will be to copy itemList, modify it, and set the state to it.
update() {
let itemList = this.state.itemList.slice();
//update it
this.setState({ itemList });
}
Best way to update data into an array of objects
onChange={ (e) => this.setState({formData: { ...this.state.formData, 'plan_id': e.target.value}})}

Update Nested state in react

Hello guys I'm trying to update the state of a nested object in react, I'm currently doing this:
handleChange({target: {id, value}}, type) {
this.setState(
state => ({
dwelling: (Object.assign(state.dwelling, {[id]: {[type]: value}}))
})
);
}
it comes from a formgroup:
<FormGroup controlId="spaces">
<ControlLabel>Dormitorios</ControlLabel>
<FormControl
componentClass="select"
value={dwelling.spaces.dorms}
placeholder="Seleccione"
onChange={e => this.handleChange(e, 'dorms')}
>
The problem is when I update the state of the sub object dwelling.spaces.dorms is created but when I try to place another property it replaces the old one instead of getting added:
Before Dwelling:
{
address: "",
currency: "",
price: 0,
publicationType: "",
spaces: {
closets: "",
dorms: "",
pools: ""
},
subtype: "",
type: ""
}
After onChange for dwelling.spaces.dorms
{
address: "",
currency: "",
price: 0,
publicationType: "",
spaces: {
dorms: "3",
},
subtype: "",
type: ""
}
After onChange for dwelling.spaces.closets
{
address: "",
currency: "",
price: 0,
publicationType: "",
spaces: {
closets: "3",
},
subtype: "",
type: ""
}
This example uses ES6 spread operator to keep your old properties which is the equivalent of Object.assign.
So what was happening is you're not keeping your nested value.
this.setState({
dwelling: {
...this.state.dwelling,
[id]: {
...this.state.dwelling[id],
[type]: value
}
}
});
In your example you overwrote your value with a new object. Notice the bolded text below.
dwelling: (Object.assign(state.dwelling, {[id]: {[type]: value}}))
In the bolded text it specifically set a new object into state.dwelling without keeping the old values. So what we did is that we used the ES6 spread operator to help merge your old values with the new value
{
...this.state.dwelling[id],
[type]: value
}
I keep my form state in a complex object and to simplify my code I use this helper function in my "handleChange" function referenced by TextField, Select, etc..
export function updateObject(obj, keys, value) {
let key = keys.shift();
if (keys.length > 0) {
let tmp = updateObject(obj[key], keys, value);
return {...obj, [key]: tmp};
} else {
return {...obj, [key]: value};
}
}
React-redux/Material-UI example
let [formState, changeFormState] = useState({});
function handleChange(event) {
changeFormState(updateObject(formState, event.target.name.split('.'),
event.target.value));
}
<TextField className={classes.textfield} name='foo.bar' value=
formstate.foo.bar || ''} onChange={handleChange} />

Resources