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].
Related
I’m trying to update a state from an input, which works fine when the field is is in the same component, but doesn’t work when I pass it into a child component, seemingly no matter what I pass to it!
const Create = () => {
const [question, setQuestion] = useState('');
const [components, setComponents] = useState([""]);
const handleQInputChange = event => {
setQuestion(event.target.value)
}
function addComponent() {
setComponents([...components, "Question"])
}
return (
<Button onClick={addComponent} text="Next question"/>
<ol>
{components.map((item, i) => (
<li>
<CreateQuestion question={question} onChange=
{handleQInputChange}/>
</li>
))}
</ol>
)}
and then CreateQuestion component looks like:
const CreateQuestion = (props) => {
function handleQInputChange( event ) {
props.onChange(event.target.value)
}
return (
<div className="Component">
<label>Question</label>
<input
name="question"
id="question"
value={props.value}
onChange={props.handleQInputChange}
type="text"
placeholder="Question"
/>
<br />
I've followed at least 10 guides on how to pass the data back and forth, so it may have become a little convoluted. If I put the Question input directly into the parent component the state updates, so I suspect it's just me not passing props correctly but completely stuck!
Thank you
You are doing a lot of wrong things:
Using wrong props.
Passing event.target.value which is a string, to props.onChange which is a
function that accepts a type Event.
Declaring the controlled input state on the parent, while you need the state local to the input, since you have multiple inputs and I don't think you want to share the same state among them.
function App() {
const [questions, setQuestions] = useState([]);
const [components, setComponents] = useState(['']);
function addComponent() {
setComponents([...components, 'Question']);
}
function addQuestion(question) {
setQuestions((qs) => [...qs, question]);
}
return (
<>
<Button onClick={addComponent} text="Next question" />
<ol>
{components.map((item, i) => (
<li>
<CreateQuestion idx={i} addQuestion={addQuestion} />
</li>
))}
</ol>
<ul>
<h5>Submitted Questions:</h5>
{questions.map((question, i) => (
<li>
<span style={{ marginRight: '10px' }}>
Question n.{question.id}
</span>
<span>{question.body}</span>
</li>
))}
</ul>
</>
);
}
const CreateQuestion = ({ addQuestion, idx }) => {
const [question, setQuestion] = useState('');
const handleChange = (event) => {
setQuestion(event.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
addQuestion({ id: idx + 1, body: question });
};
return (
<div className="Component">
<form onSubmit={handleSubmit}>
<label></label>
<input
name="question"
id="question"
value={question}
onChange={handleChange}
type="text"
placeholder="Question"
/>
<button>ADD QUESTION</button>
</form>
</div>
);
};
I refactored your code in this demo https://stackblitz.com/edit/react-h7m1cu check if it helps you.
I fixed the main issues, moved the input state down to the CreateQuestion Component, added a function and a state to the Parent Component that holds all the questions added when you submit the input, this way you can handle data in your Parent, if for example you want to send it to your server.
Please change your CreateQuestion component like below
const CreateQuestion = (props) => {
return (
<div className="Component">
<label>Question</label>
<input
name="question"
id="question"
value={props.value}
onChange={props.onChange}
type="text"
placeholder="Question"
/>
</div>
);
}
The problem is
You use an attribute like
onChange={props.handleQInputChange}
But you do not have handleQInputChange when you call your component as an attribute
<CreateQuestion question={question} onChange=
{handleQInputChange}/>
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
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 have an array of strings, and it's count in state:
const [count, setCount] = useState(0);
const [strings, setStrings] = useState([""]);
So in the beginning I always have an element in the state.
On a button click I'm adding to the count and the list:
function onClickAdd() {
setStrings(prev => [...prev, ""]);
setCount(count + 1);
}
I'm rendering multiple text fields based on this count, and the value of those text fields is set from the array index (range is a custom function, returns array of values from start -> end):
function makeInputs(count, onRemove) {
return range(1, count).map(id => {
return (
<TextInput type="url" validate onChange={e => onChange(e, id)} value={strings[id]}
icon={<Button style={{backgroundColor: "red"}} onClick={() => onRemove(id)}><Icon>remove</Icon></Button>} />
);
});
}
But whenever I click the add button, new field is rendered and the value of new fields is set to undefined (strings array is not updated yet), and I get a warning:
index.js:1 Warning: A component is changing an uncontrolled input of type url to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
How can I delay the render until list is resized?
I've tried moving setCount() to useEffect() but doing that doesn't make the new fields show up at all.
https://codesandbox.io/s/aged-dew-eud84?fontsize=14&hidenavigation=1&theme=dark
Try this
function Form() {
const [values, setValues] = useState([]);
return (
<div>
<button onClick={() => setValues([...values, ""])}>Add</button>
{values.map((value, idx) =>
<div key={String(idx)}>
<input
type="text"
value={value}
onChange={(e) => {
values[idx] = e.target.value;
setValues([...values]);
}}
/>
</div>
)}
</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}