React - object assign multiple dynamic input values - javascript

I am trying to set state of dynamically generated inputs. The initial tasks object where I want to set the new state looks like so:
The render method:
render(){
return(
<div>
<main className="content">
<form onSubmit={this.onSubmit}>
<div>
{Object.keys(this.state.dataGoal).map( (key, index) => {
return <div key={key}>
<label>{this.state.dataGoal[key]}</label>
<div className="input-wrap">
<input
type="text"
name={`${key}-task-${index}`}
value={this.state.tasks[key]}
onChange={this.handleInputChange} />
</div>
</div>;
})}
</div>
<div className="input-wrap">
<input
className="primary-btn"
type="submit"
value="Set my goal!"
onClick={this.formReset} />
</div>
</form>
</main>
</div>
);
}
and finally the handleInputChanged function:
handleInputChange = (e) => {
const value = e.target.value;
const name = e.target.name;
this.setState({
tasks: Object.assign({}, this.state.tasks, {[name]: value})
});
}
I want to set the new state of object when one of the inputs is changed. The desired result is to get the input value and set it to name key as an value in tasks object.
I also want to ask if the input names must be unique.
Thanks for any help,
Jakub

This looks like you're on the right path, the only thing missing is we will have to tell handleInputChange what index we want to update in tasks. We will have to use Object.assign twice because it's a nested structure. In this case if we assume the indices of dataGoal match up with tasks, we can pass in the index provided by the map function.
In our render function:
<input
type="text"
name={`${key}-task-${index}`}
value={this.state.tasks[key]}
onChange={(e) => this.handleInputChange(e, index)} />
// Notice: This will cause a performance hit
// We are binding 'this' using fat arrow every render but it shows the idea
Handling the input:
handleInputChange = (e, index) => {
const value = e.target.value;
const name = e.target.name;
const tasks = this.state.tasks;
const updatedTask = Object.assign({}, tasks[index], { [name]: value });
this.setState({
tasks: Object.assign({}, tasks, { [index]: updatedTask })
});
}
Input names don't have to be unique in this case, as the index will provide 'uniqueness'.
Using destructing instead of Object.assign:
handleInputChange = (e, index) => {
const value = e.target.value;
const name = e.target.name;
this.setState({
tasks: {
...this.state.tasks,
[index]: {
...this.state.tasks[index],
[name]: value
}
}
});
}

Related

Why does react input checkbox not check when onChange handler is setting state?

I have a React app with some check boxes that were mapped out that don't check anymore when the onChange handler function sets a state. However, the checkboxes do work when I am not setting a state without arrays e.g. setState("it works"). But, they don't work when I am setting a state with arrays e.g. setState([...array, value]) and instead remain unchecked. The console.log for e.target.checked remains true and visually is unchecked when I click the checkbox/trigger the onChange handler function. The checkbox works when I don't setState at all. The following is my code snippet, I don't think I am understanding why this is happening.
export default function ReserveModal({ className, openModal }) {
const [roomsData, setRoomsData] = useState([]);
const [selectedRooms, setSelectedRooms] = useState([]);}
const handleSelect = (e) => {
const checked = e.target.checked;
const value = e.target.value;
setSelectedRooms(
checked
? [...selectedRooms, value]
: selectedRooms.filter((item) => item !== value)
);
};
return (
<div className={styles.roomNumbers}>
{roomsData.map((roomNumber) => {
return (
<div key={uuidv4()} className={styles.rooms}>
<label htmlFor="roomNumber">{roomNumber.number}</label>
<input value={roomNumber.number} name="roomNumber" id="roomNumber" type="checkbox" onClick={handleSelect} />
</div>
);
})}
</div>
);
}
You don't actually set the checked attribute of the input. I think something like this should work:
export default function ReserveModal({ className, openModal }) {
const [roomsData, setRoomsData] = useState([]);
const [selectedRooms, setSelectedRooms] = useState([]);}
const handleSelect = (e) => {
const checked = e.target.checked;
const value = e.target.value;
setSelectedRooms(
checked
? [...selectedRooms, value]
: selectedRooms.filter((item) => item !== value)
);
};
return (
<div className={styles.roomNumbers}>
{roomsData.map((roomNumber) => {
return (
<div key={uuidv4()} className={styles.rooms}>
<label htmlFor="roomNumber">{roomNumber.number}</label>
<input checked={selectedRooms.includes(roomNumber.number)} value={roomNumber.number} name="roomNumber" id="roomNumber" type="checkbox" onClick={handleSelect} />
</div>
);
})}
</div>
);
}

Reset controlled value of single individual input in form upon button click

My app gets initialized with a json payload of 'original values' for attributes in a task form. There is a shared state between various components through a context manager submissionState that also gets imported to this form component. This shared state is a copy of the original values json but includes any edits made to attributes in the payload. I would like to include individual 'reset' buttons for each input of the form which would update the shared state to the original value in the json payload. The original values get passed into the parent form component as props and the edited value state gets called from within the parent component as well.
const FormInput = ({ fieldName, fieldValue, onChange, originalValue }) => {
const handleReset = e => {
//????
}
return (
<div>
<label htmlFor={fieldName}>
{fieldName}
</label>
<br />
<input type="text"
name={fieldName}
value={fieldValue}
onChange={onChange}/>
<button id="reset" onClick={handleReset}>↻</button>
<br />
<div>{originalValue}</div>
</div>
);
};
const TaskForm = (payload: TaskPayload) => {
const { submissionState, setSubmission } = useTask();
function handleChange(evt) {
const value = evt.target.value;
setSubmission({
...submissionState,
[evt.target.name]: value,
});
}
const taskFields = ["name", "address", "address_extended", "postcode", "locality", "region", "website"];
return (
<div>
<form>
{taskFields.map((field) => {
<FormInput
key={field}
fieldName={field}
fieldValue={submissionState[field]}
onChange={handleChange}
originalValue={payload[field]}
/>
})
}
</form>
</div>
);
};
export default TaskForm;
What I would like to do is include logic in the reset button function so that any edits which were made in a form input (from state) get reverted to the original value (stateless), which comes from the payload props: payload[field].
The form input is controlled through a global shared state submissionState, so the reset button logic can either modify the shared state itself with something like:
const handleReset = (submissionState,setSubmissionState) => {
setSubmission({
...submissionState,
fieldName: originalValue,
});
but I would need to pass the submissionState and setSubmission down through to the child component. It would be better if I can somehow update the value attribute in the input, which in-turn should potentially update the shared state? And the logic can just be something like this (assuming I can somehow access the input's value state in the reset button)
const handleReset = (?) => {
/*psuedo code:
setInputValueState(originalValue)
*/
}
I would highly recommend using react-hook-form if it's an option. I've implemented it across several projects and it has never let me down. If it's just not possible to use a library, then keep in mind that React is usually unidirectional. Don't try to work around it, since it works that way by design for most cases you can encounter. Otherwise…
const TaskForm = (payload: TaskPayload) => {
const { submissionState, setSubmission } = useTask();
const upsertSubmission = (upsert) =>
setSubmission({
...submissionState,
...upsert,
});
const handleChange = ({ target }) => {
upsertSubmission({
[target.name]: target.value,
});
};
const reset =
(originalValue) =>
({ target }) => {
upsertSubmission({
[target.name]: originalValue,
});
};
/* Also something like this. RHF will handle most of this for you!
* const reset = (originalValue, fieldName) =>
* upsertSubmission({[fieldName]: originalValue})
*/
const taskFields = [];
return (
<div>
<form>
{taskFields.map((field) => (
<FormInput
key={field}
fieldName={field}
onChange={handleChange}
reset={reset(originalValue)}
value={submissionState[field]}
/>
))}
</form>
</div>
);
};

How can i get the Id of the Todo

am trying to delete am item by id but i keep get error, that each child should have a unique key after giving a it an id, what am i doing wrongly, why am i not getting the id
const TodoList = () => {
const [input, setInput] = useState("");
const [todos, setTodos] = useState([])
const handleSubmit = e => {
e.preventDefault()
setTodos([...todos, input])
setInput("")
}
const handleDelete = id => {
let item = todos.filter(todo => todo.id !== id)
console.log(item)
// setTodos(item)
}
return (
<div className='todolist'>
<h2>Todo List</h2>
<form>
<input value={input} onChange={e => setInput(e.target.value)} placeholder='write something' />
<button onClick={handleSubmit}>Add todo</button>
</form>
{todos.map(todo => (
<div key={todo.id} className='todolist__details'>
<h2>{todo}</h2>
<DeleteIcon onClick={() => handleDelete(todo.id)} />
</div>
))}
</div>
);
};
export default TodoList;
From the above code, it looks like todos is an array of strings. So when you are assigning key by doing todo.id, you are assigning the key to be undefined since the id property does not exist in the string type. Change you map method like this
{todos.map((todo, i) => (
<div key={i} className='todolist__details'>
<h2>{todo}</h2>
<DeleteIcon onClick={() => handleDelete(i)} />
</div>
))}
and then change on your handleDelete like
const handleDelete = id => {
const newTodos = [...todos];
newTodos.splice(id, 1);
console.log(newTodos)
setTodos(newTodos)
}
{todos.map((todo, i) => {
<div key={i} className='todolist__details'>
<h2>{todo}</h2>
<DeleteIcon onClick={() => handleDelete(i)} />
</div>
})}
You can check how list and keys work in react in here
https://reactjs.org/docs/lists-and-keys.html
You have not given id to any of your todo , Pass the index of that todo instead of id , it will solve your problem
Try this sandbox code link
I hope you find this helpful.
As other users (especially TheWhiteFang) pointed out above, your todos list is an array, and your single todo item inside this array is string which you get from your input.
Alternatively, you could set your single todo item as an object instead of plain string, such as {id: 1, content: input}, for example, change the line of setTodos to:
setTodos([...todos, { id: count, content: input }]);
In this way, you could then access the id of every single todo item and access the content of the todo item via todo.content, when using a map function.
To illustrate this, you may refer to this code snippet:
https://codesandbox.io/s/todo-list-in-5min-2tomwb?file=/src/App.js

Updating object in react js in a function based component

I have this imaginary case where I have a function that has a form, which I can update my data. It is a real estate agency and wants to have option to update its properties when the sellers makes some changes. Note that data might not make much sense irl but it is just for learning purpose.
A property object looks like this:
{
owner: "John doe",
other: {
rooms: 3,
windows: 6
}
address: {
street: "jane doe",
houseNr: 24
}
}
And my component:
import React, { useState } from 'react'
import { connect } from 'react-redux'
import { getProperty } from '../../actions'
const Property = ({ getProperty, property }) => {
const [propertyObj, setPropertyObj] = useState(property);
const { propertyId, owner, address, other } = propertyObj
const propertyAddress = `${address.street}, ${address.houseNr}` // jane doe, 24
const { rooms } = other;
const handleSubmit = (event) => {
event.preventDefault()
// todo: submit the updated property
}
return (
<div className='row align-items-start'>
<div className='col col-md-6'>
<p>Id: {propertyId}</p>
<form>
<div className='row'>
<label className='mt-3'>Owner</label><input className='ml-3' type='text' name='owner' onChange={e => setPropertyObj({ owner: e.target.value })} defaultValue={owner} />
</div>
<div className='row'>
<label className='mt-3 mx-3'>Rooms</label><input className='ml-3' type='text' name='rooms' onChange={e => setPropertyObj({ rooms: e.target.value })} defaultValue={rooms} />
<div className='row'>
<label className='mt-3 mx-3'>Property Address</label><input className='ml-3' type='text' name='propertyAddress' onChange={e => setPropertyObj({ propertyAddress: e.target.value })} defaultValue={propertyAddress} />
</div>
</form>
</div>
<button onClick={(e) => handleSubmit(e)} className='btn btn-success'>Update Property</button>
</div>
)
}
const mapStateToProps = reduxStoreState => {
return {
property: reduxStoreState.property,
}
}
export default connect(mapStateToProps, { getProperty })(Property)
When attempting to update rooms property, I get:
TypeError: other is undefined
But I beliver I would get a similar error when I would try to update the property address.
I know that for property address, I have to split into street and house number to update each separately. The question is how can I update the state variable property so that I can submit that to my function updatePropertyById(id, updatedProperty), I have seen ways to do in cases when it is a class object, but I want to use the new features of react.
How can I update the rooms for instance, without needing to also type the windows, I think the spread operator would be helpful but I just don't know how.
Updating a json object via spread operator
To use the spread operator, you have to know the level of the key. Lets assume we have an object:
const obj = {"field1": {"field2": 1, "field3": 2}, "field4":3}
To update field3 you have to do:
obj = {...obj, field1: {...field1, field3: newValue}}
In your case
This is a little different in your case, since you want to give the key and update the object. One way of doing this is traversing the object
const updateProperty = (obj, key, newVal) => {
let tempObj = {...obj}
Object.keys(tempObj).forEach(k => {
if(k===key) tempObj[k] = newVal
})
setPropertyObj(tempObj)
}
In your code, you've written:
...
onChange={e => setPropertyObj({ rooms: e.target.value })}
...
This overrides the previous value of propertyObj. What you need to do is to keep the previous state using the spread operator:
onChange={e => setPropertyObj(state => ({ ...state, rooms: e.target.value })}
Or even better, you can write a function for this:
function handleChange(e) {
setPropertyObj(state => ({
...state,
[e.target.name]: e.target.value
})
}
...
<input className='ml-3' type='text' name='rooms' onChange={handleChange} defaultValue={rooms} />
...
UPDATE: I think by other, you meant to use the rest syntax in object destructring.
The code { other } = propertyObj means that you're looking for a property with key other inside propertyObj. If you want to assign the remaining properties to a newly defined variable, you should prepend it with three dots:
const { propertyId, owner, address, ...other } = propertyObj

Why won't my controlled Input component in React update with the state?

I've got a list of ingredients in an "editIngreds" array in my state, e.g. ['Apple', 'Banana', 'Orange']. They are rendered to the DOM via react map function. I have an edit mode that replaces the text with Input boxes so I can edit the items and save them back to the state array. However, when typing in the box, nothing happens. console.log shows that editIngreds[i] is indeed being updated when I type a character, however the input boxes do not update with this new value.
export default function RecipeCard({ recipe, onEditRecipe, onRemoveRecipe }) {
const ingredients = Object.values(recipe.ingredients);
const ingredientKeys = Object.keys(recipe.ingredients);
const [editIngreds, setEditIngreds] = React.useState(ingredients)
...
const onChangeEditIngreds = (event, i) => {
const value = event.target.value;
const ingreds = editIngreds
ingreds[i] = value;
setEditIngreds(ingreds);
};
...
return (
<ul>
{ingredients.map((ingred, i) => (
<li key={ingredientKeys[i]}>
{editMode ? (
<Input
type="text"
value={editIngreds[i]}
onChange={(e) => onChangeEditIngreds(e,i)}
/>
) : (
<>{ingred}</>
)}
</li>
))}
</ul>
You are mutating the state. Make a shallow copy of ingredients before updating
const onChangeEditIngreds = (event, i) => {
const value = event.target.value;
const ingreds = [...editIngreds];
ingreds[i] = value;
setEditIngreds(ingreds);
};
Rather than using this -> ingredients.map((ingred, i)
use this ->
editIngreds.map((ingred, i){
....
<Input
type="text"
value={ingred}
onChange={(e) => onChangeEditIngreds(e,i)}
/>

Categories

Resources