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
Related
I made one page in react.js project that has a text input and a button for each row of data.
I want to get each input value by on click button and do process over it.
{data.map((item, index) => <div><span>{item}</span>
<input type="text" placeholder='enter value' />
<button onClick={() => {alert(inputValue) }}>
click me
</button>
</div>
)}
You can do something like this
const data = [0, 1, 2, 3];
export default function App() {
const [inputs, setInputs] = useState({});
return (
<div className="App">
{data.map((item) => (
<div key={item}>
<input
onChange={(e) =>
setInputs((prev) => {
return { ...prev, [item]: e.target.value };
})
}
/>
<button onClick={() => alert(inputs[item])}>Click</button>
</div>
))}
</div>
);
}
Codesandbox: https://codesandbox.io/s/divine-wildflower-1ge7ev?file=/src/App.js:58-550
The correct react way of doing this would be to create a standalone component e.g. UserInput and keep track of user input's state:
// Create state
const [input, setInput] = React.useState('')
return (
<input type="text" placeholder='enter value'
onChange={event => setInput(event.target.value)}
/>
<button onClick={() => {alert(input) }}>
click me
</button>
);
You can then add this to your current component as follows:
{data.map((item, index) => <div><span>{item}</span><UserInput/></div>)}
I suggest you use an object instead of an array. It's more appropriate for key-value pairs.
If you use an object, it will be easier, readable and more maintainable.
How to do this?
You just need to modify your code to loop through the keys of your object then every time the input value changes (onChange), then set the value of the object onChange={(e) => data[key]=e.target.value}. To access the value when the button of the field is clicked, just use the data[key].
I have an array of objects and I need the keys and values to be editable, I was given this approach : https://codesandbox.io/s/silly-gagarin-j8cfi?file=/src/App.js
But as you can see, the inputs are all empty.
I have tried using defualtValue but that will cause problems later I believe. The aim of this is to eventually compare these values to a database later on.
Please can someone take a look at the sandbox I linked above and help me out?
You need to use Object.entries which will give you access to both the key and value of each property. Your example was not working because you were trying to destructure Object.values as an object when it returns an array.
See my working code below:
https://codesandbox.io/s/inspiring-swirles-208cm?file=/src/App.js
export default function App() {
const [data, setData] = useState(baseData);
const updateValue = (index) => (e) => {
setData((prev) => {
const copiedData = [...prev];
copiedData[index][e.target.name] = e.target.value;
return copiedData;
});
};
return (
<>
<div>
{data.map((item, index) => (
<div key={item.id}>
{Object.entries(item).map(([key, value]) => (
<input
name={key}
type="text"
id={item.id}
key={item.id}
value={value}
onChange={updateValue(index)}
/>
))}
</div>
))}
</div>
</>
);
}
I want to get all elements with the classname selected from this component
function ChooseElements() {
const listItems = elementObjects.map((object) =>
<ListItem key={object.id.toString()} value={object.Element} />
);
return (
<div> <ul>
{listItems}
</ul>
<button onClick={ console.log("get all list items")}>get Elements</button>
</div>
);
}
in plain js i could use document.getElementsByClassName('selected') to get all elements
I have read somewhere that useRef should be used
to access the elements in the virtual dom, is this correct and how can I do this ?
i think you can use document.querySelectorAll('.list-item') but you need to assign a classname first. example my class is list-item so you can get the DOM elements.
You should probably see it from a different perspective. You can lift your state one level up to your <ChooseElements /> component.
This is the practice encouraged by react for similar problems. here
function ChooseElements() {
const [selectedItems, setSelectedItems] = useState([]);
const handleItemSelect = item => {
setSelectedItems([...selectedItems, item]);
}
const handleItemUnselect = item => {
setSelectedItems(selectedItems.filter(i => i !== item));
}
const listItems = elementObjects.map((object) =>
<ListItem
key={object.id.toString()}
value={object.Element}
onSelect={() => handleItemSelect(object)}
onUnselect={() => handleItemUnselect(object)}
/>
);
return (
<div>
<ul>
{listItems}
</ul>
<button onClick={() => console.log(selectedItems)}>get Elements</button>
</div>
);
}
However, if ONLY AND ONLY lifting state up is not possible to you, you can use ref like this:
function ChooseElements() {
const myRef = useRef();
const listItems = elementObjects.map((object) =>
<ListItem
key={object.id.toString()}
value={object.Element}
/>
);
return (
<div>
<ul ref={myRef}>
{listItems}
</ul>
<button onClick={() => {
console.log(ref.current.querySelector('.selected'));
}}>
get Elements
</button>
</div>
);
}
I am making a simple application where the user can create a new form on a button click, so for this, I have an array state like this :
const [numbers, setNumbers] = useState([0]);
const [count, setCount] = useState([0]);
And on my button onClick method I have this,
setCount(count + 1);
setNumbers(numbers.concat(numbers[0] + count));
In my render method, I have :
{numbers.map((number) => {
return (
<div key={number}>
<InputCreator id={number} value={number} />
</div>
);
})}
And my InputCreator component is a simple callback component with few textfields.
So far, it works well. Lately I wanted to add a delete functionality where the user can delete that particular form. So, I added.a button inside this form and on the onClick method, I tried console loging the "numbers" state to check everything is working, but it logs only the default value I have given while creating the state and not the updated state. What could be the reason ? So my idea is to delete that index from the array using the props passed, so that the component will re-render with the updated number of forms. Is there a better way to do this ?
EDIT : This is my InputCreator component,
const InputCreator = useCallback((props) => {
const { id, value } = props;
// console.log(titles[id]);
return (
<div>
<Col xs={12} md={8}>
<div className={styles.container}>
<Form
noValidate
validated={false}
onSubmit={handleSubmit}
encType="multipart/form-data"
>
<Form.Group controlId="formGroupTitle">
<Form.Label>Title</Form.Label>
<Form.Control
type="text"
placeholder="Title"
onChange={(e) => handleChange(e, value)}
name="title"
value={titles[id]}
/>
</Form.Group>
<Form.Group controlId="formGroupTitle">
<Form.Label>Description</Form.Label>
<Form.Control
type="text"
name="description"
placeholder="Max limit 30"
onChange={(e) => handleChange(e, value)}
maxLength={31}
value={descriptions[id]}
/>
</Form.Group>
<Button
variant="outline-primary"
size="sm"
className={styles.deleteBtn}
onClick={(e) => handleDelete(e, number)}
>
X
</Button>
</Form>
)})
handleDelete :
const handleDelete = (e, value) => {
e.preventDefault();
console.log(numbers);
}
I will just point out the mistakes I see as your question is not worded clearly.
Keys aren't passed into they are passed into list in order to create a list for every element with a unique ID. I am not sure what you are trying to do in your render method but I would suggest doing something like this :
const renderNumbers = () => myArray.map((number, index) =>
<MyComponent key={index} number={number} />
To delete an element. Create a function that takes in the ID. Filter it to show a new array without that ID, below is an example that you ca apply.
const deletePerson = (id) => {
setNumbers(numbers.filter((number) => number.id !== id));
};
Send this handle to the button that deletes the element
const handleDeleteNumber = () => {
deleteNumber(number.id);
};
Make a single handleInputChange() that you can use for all input changes. Such as this one :
const handleInputChange = (event) => {
setInfo({ ...info, [event.target.name]: event.target.value });
};
In the tag you pass in id and values separately like this
value={descriptions}
key={id}
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
}
}
});
}