I'm to add/remove the row dynamically on clicking the button. When I add its adding properly like when there are 1,2,3,4 rows and when I click add on 2 row its adding new row as 3. But when I delete the particular row, its always deleting the last row. Here I've passed the index from map, but even then its removing last element only.
https://codesandbox.io/s/add-remove-items-p42xr?file=/src/App.js:0-1099
Here is a working snippet. It uses a ref to keep track of id's to avoid duplicates, and uses element.order as key instead of index. The remove method has been changed to use a callback passed to setState and a filter() call to remove the elements based on passed order property.
const { useState, useRef } = React;
const App = () => {
const [formValues, setFormValues] = useState([
{ order: 1, type: "", name: "", query: "" }
]);
const id_index = useRef(1);
let addFormFields = () => {
setFormValues([
...formValues,
{ order: (id_index.current += 1), type: "", name: "", query: "" }
]);
};
let removeFormFields = (order) => {
setFormValues(prev => prev.filter(element => element.order !== order));
};
return (
<div>
{formValues.length ?
formValues.map((element) => (
<div className="form-inline" key={element.order}>
<label>{element.order}</label>
<input type="text" name="hello" />
<button
className="button add"
type="button"
onClick={() => addFormFields()}
disabled={formValues.length >= 4}
>
Add
</button>
<button
type="button"
className="button remove"
onClick={() => removeFormFields(element.order)}
>
Remove
</button>
</div>
))
: null}
</div>
);
};
ReactDOM.render(
<App />,
document.getElementById("app")
);
<script crossorigin src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Two things you have to update,
update the addFormFields method
update the removeFormFields method
https://codesandbox.io/s/add-remove-items-forked-so-sqoqk?file=/src/App.js:378-394
Here is the codesanbox link for your reference.
Related
I'm writing a program where you can add reviews to a list in React. I also added a feature to delete the reviews. Each review is a component stored in a State array. I wrote a function, removeItem, that updates the state by creating a duplicate of the array and popping the passed index". Each review is given a handleFeature property where this removeItem function is passed, and an id which corresponds to it's index in the array.
Inside the review component, it has an onclick event which calls the removeItem function through handleFeature, passing it's own id in the array. I thought this would cause the array to update and remove the item; however, It causes multiple items to get deleted for no apparent reason. Does anyone know the fix to this issue
Data
export default[
{
id: 0,
name: "Mindustry",
score: "4.5"
},
{
id: 1,
name: "Minecraft",
score: "4"
},
{
id: 2,
name: "Plants vs Zombies",
score: "4"
},
]
App
import './App.css';
import jSData from './components/jSData.js'
import Card from './components/Card.js'
import React from 'react';
function App() {
//we are mapping the review data to an array of cards using an lamba expression
//this is a state object. Once it changes, the webpage is updated
//it returns the object and a function to change it
//the object is immutable; however, you can reference it to make updates
const [reviews, changeState] = React.useState(jSData.map((item) => {
return (<Card
//key is necessary for list items
key = {item.id}
handleEvent = {removeItem}
//name = {item.name}
//score = {item.score}
//the above can be simplified to
{...item}
/>);
}
));
function submit(e)
{
//prevent reloading
e.preventDefault();
//spreading the original array + card into a new array
/*changeState(
[...reviews,
<Card
id = {reviews.length}
name = {document.getElementById('form_name').value}
score = {document.getElementById('form_score').value}
/>]
);*/
//best practice to use the higher order version of state change
//this should contain a function which returns the new state
changeState(oldValue =>
[...oldValue,
<Card
id = {reviews.length}
key = {reviews.length}
handleEvent = {removeItem}
name = {document.getElementById('form_name').value}
score = {document.getElementById('form_score').value}
/>]
);
}
function removeItem(id)
{
changeState(reviews.map(x => x).pop(id))
}
return (
<div id = "wrapper">
<form id = "review-form">
<h1>Review</h1>
<input className = "review-text" placeholder="Name" id = "form_name"/>
<input className = "review-text" placeholder="Score" id = "form_score"/>
<input id = "review-button" type = "Submit" onClick = {submit}/>
</form>
<ul id = "card-holder">
{reviews}
</ul>
</div>
);
}
export default App;
Review Component
import React from "react";
export default function Card(item)
{
function handle()
{
console.log(item.handleEvent);
item.handleEvent(item.id)
}
//conditional rendering with and statements
return(
<div className = "card-wrapper">
<div className = "card">
<h2>{item.name}</h2>
<h4>{item.score} / 5</h4>
</div>
<span class="material-symbols-outlined" onClick = {handle}>close</span>
</div>
);
}
Here's a simplified and fixed example. As I said in the comment, don't put elements in state; just map your data into elements when returning your markup.
The initial data is returned by a function, so we dont accidentally mutate "static data" from another module
The Card component now accepts the item as a prop, not "spread out"
Removal actually works (we filter the state array so there's only items without the ID-to-remove left)
import React from "react";
function getInitialData() {
return [
{
id: 0,
name: "Mindustry",
score: "4.5",
},
{
id: 1,
name: "Minecraft",
score: "4",
},
{
id: 2,
name: "Plants vs Zombies",
score: "4",
},
];
}
function Card({ item, onRemove }) {
return (
<div className="card-wrapper">
<div className="card">
<h2>{item.name}</h2>
<h4>{item.score} / 5</h4>
</div>
<span
className="material-symbols-outlined"
onClick={() => onRemove(item.id)}
>
close
</span>
</div>
);
}
function App() {
const [data, setData] = React.useState(getInitialData);
function removeItem(id) {
setData((reviews) => reviews.filter((item) => item.id !== id));
}
function submit(e) {
e.preventDefault();
setData((reviews) => {
// TODO: do these with refs or state instead of getElementById
const name = document.getElementById("form_name").value;
const value = document.getElementById("form_score").value;
const newReview = {
id: (+new Date()).toString(36),
name,
value,
};
return [...reviews, newReview];
});
}
return (
<div id="wrapper">
<form id="review-form">
<h1>Review</h1>
<input
className="review-text"
placeholder="Name"
id="form_name"
/>
<input
className="review-text"
placeholder="Score"
id="form_score"
/>
<input id="review-button" type="Submit" onClick={submit} />
</form>
<ul id="card-holder">
{data.map((item) => (
<Card key={item.id} item={item} onRemove={removeItem} />
))}
</ul>
</div>
);
}
I have a questions form with custom inputs.
const Input = (props) => {
return (
<div>
<label className={classes.label}>{props.label}
<input className={classes.input} {...props}/>
</label>
</div>
);
};
I get question list from server and set them into questions. Then I create a form for answer to these questions.
<form onSubmit = {onAnswersSubmit}>
{questions?.map((item) =>
<Input key={item.id} id={item.id} label={item.question}/>)}
<Button> Submit </Button>
</form>
I'd like to push answers from inputs into array on submit button click, but have no idea how to do that.
You should probably use state for this. One state for the array (I've called it state), and another to capture the answers to the questions (an object).
When an input's onChange listener is fired it calls handleChange. This function takes the name and value from the input (note: this example assumes that you can add a name property to the data you receive from your server), and then updates the answers state.
When the button is clicked the completed answers state (an object) gets added to the main state array.
const { useEffect, useState } = React;
function Example({ data }) {
// Initialise the states
const [ state, setState ] = useState([]);
const [ answers, setAnswers ] = useState({});
// Push the completed answers object into
// the state array, then reset the answers state
function handleClick() {
setState([ ...state, answers ]);
setAnswers({});
}
// Get the name and value from the input
// and update the answers state
function handleChange(e) {
const { name, value } = e.target;
setAnswers({ ...answers, [name]: value });
}
// Log the main state when it changes
useEffect(() => console.log(state), [state]);
return (
<div>
{data.map(obj => {
const { id, name, question } = obj;
return (
<input
key={id}
name={name}
placeholder={question}
onChange={handleChange}
/>
);
})}
<button onClick={handleClick}>Submit</button>
</div>
);
}
const data = [
{ id: 1, name: 'name', question: 'What is your name?' },
{ id: 2, name: 'age', question: 'How old are you?' },
{ id: 3, name: 'location', question: 'Where do you live?' }
];
ReactDOM.render(
<Example data={data} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You can use the html input's onChange prop and pass an onChange function from your parent form to handle that like so:
// This is assuming this is inside a functional component
const [answers, setAnswers] = useState([]);
const onAnswerChange = useCallback((index, answer) => {
setAnswers((oldAnswers) => {
oldAnswers[index] = answer;
return oldAnswers;
});
}, [setAnswers]);
return
<form onSubmit = {onAnswersSubmit}>
{questions?.map((item, index) =>
<Input key={item.id}
id={item.id}
label={item.question}
value={answers[index] ?? ''}
onChange={(e) => onAnswerChange(index, evt.target.value)}/>)}
<Button> Submit </Button>
</form>
Then you can use the stored state in answers as part of your onAnswersSubmit callback.
const [answers, setAnswers] = useState([]);
const handleAnswerChange = (index, answer) => {
let olddata=answers;
olddata[index]=answer;
setAnswers([...olddata]);
}
const Input = (props) => {
return (
<div>
<label className={classes.label}>{props.label}
<input className={classes.input} {...props}/>
</label>
</div>
);
};
<form onSubmit = {onAnswersSubmit}>
{questions?.map((item) =>
<Input key={item.id} id={item.id} label={item.question} onChange={handleAnswerChange}/>)}
<Button> Submit </Button>
</form>
i am using react.i have 2 inputs that by clicking a button dynamically ganerats.
this the code:
useEffect(() => {
const myFields = fieldsArray.map((field) => {
return (
<div key={field} className="col-12 m-auto d-flex">
<input id={field} name={`question${field}`} onChange={handleChangeFields} placeholder='سوال' className="form-control col-4 m-2" />
<input id={field} name={`answer${field}`} onChange={handleChangeFields} placeholder='جواب' className="form-control col-4 m-2" />
</div>
)
})
setFields(myFields)
}, [fieldsArray])
i need to save value of 2 inputs together in an opjects like this :
[{question : '',answer : ''}]
and the new 2 input's value are going to update the above array like this:
[{question : '',answer : ''}, {question : '',answer : ''}]
i can save each input's value separately like this :
const handleChangeFields = (e) => {
const { name, value } = e.target
setFieldsValue([...fieldsValue, { [name]: value }])
}
but i want each 2 input's value together
i need to save each 2 inputs together.
how do i do that?
This is a general example of how I think you can tie in what you're currently doing, and add in as little new code as possible. This logs the new data state when the button is clicked.
Have two states. One for collecting the updated form information (form), and another for the combined form data array (data).
Have a form with a single onChange listener (event delegation) so you can catch events from all the inputs as they bubble up the DOM. That listener calls the handleChange function which updates the form state.
Have a button with an onClick listener that calls handleClick which takes the current form state and adds it to the data state.
I would be a bit wary of storing JSX in state. You should have that map in the component return and only updating the actual field data with basic information.
One final issue - your inputs cannot have the same id. ids must be unique. I'd get rid of them altogether.
const { useEffect, useState } = React;
function Example() {
// Initialise two states. `data` is an array
// and `form` is an object
const [ data, setData ] = useState([]);
const [ form, setForm ] = useState({});
// Add the form data to the `data` array
function handleClick() {
setData([ ...data, form ]);
}
// We're using event delegation so we need to
// check what element we changed/clicked on
// - in this case the INPUT element. We can then
// update the form state with the name/value pair of that input
function handleChange(e) {
const { nodeName, name, value } = e.target;
if (nodeName === 'INPUT') {
setForm({ ...form, [name]: value });
}
}
// Just logs the data state after a change
useEffect(() => console.log(data), [data]);
return (
<form onChange={handleChange}>
<input name="question" type="text" />
<input name="answer" type="text" />
<button
type="button"
onClick={handleClick}
>Update form
</button>
</form>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
this problem solved for me :
function App() {
const [results, setResults] = useState([{ question: "", answer: "" }])
// handle input change
const handleInputChange = (e, index) => {
const { name, value } = e.target
const list = [...results]
list[index][name] = value
setResults(list)
}
// handle click event of the Remove button
const handleRemoveClick = (index) => {
const list = [...results]
list.splice(index, 1)
setResults(list)
}
// handle click event of the Add button
const handleAddClick = () => {
setResults([...results, { question: "", answer: "" }])
}
// handle submit to servers
const handlesubmit = () => {
// axios
console.log(results)
}
return (
<div className="App">
{results?.map((result, index) => {
return (
<div key={index}>
<input
name="question"
value={result.question}
onChange={(e) => handleInputChange(e, index)}
/>
<input
name="answer"
value={result.answer}
onChange={(e) => handleInputChange(e, index)}
/>
{results.length !== 1 && (
<button onClick={() => handleRemoveClick(index)}>
Remove
</button>
)}
{results.length - 1 === index && (
<button onClick={handleAddClick}>add</button>
)}
</div>
)
})}
<div style={{ marginTop: 20 }}>{JSON.stringify(results)}</div>
<button onClick={handlesubmit}>submit</button>
</div>
)
}
export default App
I am trying to add a new fields in the useState hook
// prevstate
let [currentdata, setcurrentdata] = useState({
id:'',
name:''
})
I try to add new fields in the object like that
setcurrentdata(currentdata => ({
...currentdata,
q: quantity,
}))
but it did not add new fields
The code snippet below may be one possible solution to achieve the desired objective.
NOTE: I personally dislike the idea of using useState in conjunction with such objects & prefer multiple simpler useState on each variable instead.
Code Snippet
const {useState} = React;
const MyFunction = () => {
const [badStateObj, setBadStateObj] = useState({
id: '',
name: ''
});
const clickHandler = (propName) => setBadStateObj(prev => ({
...prev,
[propName]: prev[propName] || ''
}));
const updateHandler = (propName, propVal) => setBadStateObj(prev => ({
...prev,
[propName]: propVal
}));
return (
<div>
{Object.entries(badStateObj).map(([k,v]) => (
<div>
Key: {k} Value: {v}
<button
onClick={() => updateHandler(k, prompt(
`Enter new value for ${k}`
))}
>
Update {k}
</button>
</div>
))}
<button
onClick={() => clickHandler(prompt('Enter prop name'))}
>
Add new prop
</button>
</div>
);
};
ReactDOM.render(
<div>
<h4>Demo showing the useState that I personally dislike</h4>
<MyFunction />
</div>,
document.getElementById("react")
);
<div id="react"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
Explanation
The badStateObj is initialized with only id and name props
When user clicks the Add new prop button, a new prop may be added
Separate buttons exist to update the value of each individual prop
clickHandler adds the new prop entered by user with '' value.
updateHandler accepts propName and propVal as parameters and updates the appropriate props within the badStateObj.
I'm trying an element from an array using the id of the element yet not successful.
constructor(props){
super(props)
this.state = {
arr : [<Input id="1" />,<Input id="2" />,<Input id="3" />,<Input id="4" />],
id: 5
}
}
addToArray(){
let id = this.state.id
let arr = this.state.arr
let tmp = <Input id={id} />
id = id + 1
arr.push(tmp)
this.setState({arr,id})
}
removeFromArray(){
let idx = prompt("Enter index")
let data = [...this.state.arr]
let arr_two = []
arr_two = data.filter(function( element ) {
return (element.props.id !== idx );
});
this.setState({arr: arr_two})
}
render(
return(){
<Button onClick={() => {this. addToArray()}}>add to array</Button>
<Button onClick={() => {this.removeFromArray()}}>remove from array</Button>
}
)
no matter what value the user enters, only the last element is being removed. Let say the user inputs 2, the element with the id 4 is gets removed instead of 2 although it exists in the array. How should I modify to resolve the issue
You have a number of syntax errors in your original code, and you're not following best practices (e.g. if you're referencing state when using setState, use the callback because setState is asynchronous), but I think the primary issue you were having is that you're comparing strings to numbers.
I refactored your code to use more current methods (i.e. ES6) and added parseInt to convert the input from window.prompt into a number that can be compared to your <Input /> component’s id prop. I also switched the explicit <Input /> declarations to use a numeric input, rather than a string.
Hit run and you'll see that it works as-is.
const Input = ({ id }) => <div>{id}</div>
class Foo extends React.Component {
state = {
arr: [
<Input id={1} />,
<Input id={2} />,
<Input id={3} />,
<Input id={4} />,
],
id: 5,
}
addToArray = () => {
const arr = [...this.state.arr, <Input id={this.state.id} />]
this.setState(prevState => ({ arr, id: prevState.id + 1 }))
}
removeFromArray = () => {
const id = parseInt(window.prompt("Enter index"))
this.setState(prevState => ({
arr: prevState.arr.filter(element => element.props.id !== id),
}))
}
render() {
return (
<div>
<div>
<button onClick={this.addToArray}>Add to array</button>
</div>
<div>
<button onClick={this.removeFromArray}>Remove from array</button>
</div>
Array: {this.state.arr}
</div>
)
}
}
ReactDOM.render(<Foo />, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>