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>
Related
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.
ChildComponent displays different fragments depending on the index passed in. This works fine, but if I have input element on multiple fragments and I put a value in one it gets automatically copied to the others. Why is this happening and how can I stop it?
const { Fragment } = React;
const fragments = (onChangeHandler) =>
[
<input type="text" id="screen1_input1" onChange={onChangeHandler} />,
<input type="text" id="screen2_input1" onChange={onChangeHandler} />
];
const ChildComponent = ({ index, fragments }) => {
const onChange = e => {
const { target: {id, value} } = e;
console.log(id, value);
const newData = {
...contentData,
[e.target.id]: e.target.value
}
setContentData(newData)
};
return (
<Fragment>
<h2 className="screens">{fragments(onChange)[index]}</h2>
</Fragment>
);
};
const ParentComponent = props => {
return <ChildComponent index={1} fragments={fragments}/>;
};
ReactDOM.render(<ParentComponent />, document.getElementById("react"));
Give them unique keys like so:
const fragments = (onChangeHandler) =>
[
<input key="key1" type="text" placeholder="input 1" id="screen1_input1" onChange={onChangeHandler} />,
<input key="key2" type="text" placeholder="input 2" id="screen2_input1" onChange={onChangeHandler} />
];
Here a Sandbox to demonstrate it: https://codesandbox.io/s/keen-sun-vsk3e?file=/src/App.js:709-710
React uses the key prop to understand the component-to-DOM Element relation, which is then used for the reconciliation process. It is therefore very important that the key always remains unique, otherwise there is a good chance React will mix up the elements and mutate the incorrect one.
Reference: https://stackoverflow.com/a/43892905/1927991
I am pretty new to React, I have worked on react native before, so I am quite familiar with a framework. Basically I have an array of objects, lets say in contains 5 items. I populated views based on the amount of objects, so if there are 5 objects, my map function would populate 5 together with 5 inputs. My question is how can I get a value of each input?
Here is my code:
array.map(map((item, index) => (
<h1> item.title </h1>
<input value={input from user} />
)
You have to use the state and update the value with onChange manually
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
value: ''
}
}
handleInputChange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
render () {
return (
<div>
<input value={this.state.value} onChange={(e) => {this.handleInputChange(e)}} />
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'))
A quick solution would be to use an array for all the input values.
const Inputs = ({array}) => {
const [inputs, setInputs] = useState([]);
const setInputAtIndex = (value, index) => {
const nextInputs = [...inputs]; // this can be expensive
nextInputs.splice(index, 1, value);
setInputs(nextInputs);
}
return (
...
array.map((item, index) => (
<div key={index}>
<h1>{item.title}</h1>
<input
value={inputs[index]}
onChange={({target: {value}) => setInputAtIndex(value, index)}
/>
</div>
)
...
);
}
Keep in mind here that in this case every time an input is changed, the inputs state array is copied with [...inputs]. This is a performance issue if your array contains a lot of items.
I'd like to add a new input everytime the plus icon is clicked but instead it always adds it to the end. I want it to be added next to the item that was clicked.
Here is the React code that I've used.
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add</i>
<i>Remove</i>
</div>
</div>
);
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [Input]
};
}
addInput = index => {
this.setState(prevState => ({
choices: update(prevState.choices, { $splice: [[index, 0, Input]] })
}));
};
render() {
return (
<div>
{this.state.choices.map((Element, index) => {
return (
<Element
key={index}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"));
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
I must admit this get me stuck for a while but there was a problem with how react deals with key props. When you use an index as a key it doesn't work. But if you make sure inputs will always be assigned the same key even when the list changes it will work as expected:
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add </i>
<i>Remove</i>
</div>
</div>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [],
nrOfElements: 0
};
}
addInput = index => {
this.setState(prevState => {
const choicesCopy = [...prevState.choices];
choicesCopy.splice(index, 0, `input_${prevState.nrOfElements}`);
return {
choices: choicesCopy,
nrOfElements: prevState.nrOfElements + 1
};
});
};
componentDidMount() {
this.addInput(0);
}
render() {
return (
<div>
{this.state.choices.map((name, index) => {
return (
<Input
key={name}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
Some reference from the docs:
Keys should be given to the elements inside the array to give the
elements a stable identity...
...We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state.
so I am learning react js and i have stumbled upon a problem which i can't seem to solve. So i have one input that sets the number of break points, and as that number get bigger, more inputs are rendered, and those inputs are for giving a value for each 'breakpoint'. Now this is what i can't seem to figure out, if I type in for example '20' and '30' they are added to the array, no problem, however if I want to change the value of the first one(20) to a lower value, let's say 10 I can't figure out how to remove the existing 20 and replace it with a new one(10)...
Here's the codepen: https://codepen.io/anon/pen/MVZMRq
so far i have this:
class App extends React.Component {
constructor() {
super();
this.state = {
breakPointsCount: 0,
range: []
}
}
addBreakPoints(event) {
this.setState({
breakPointsCount: parseInt(event.target.value, 10),
progress: 0,
range: []
});
}
addBreakPointValue(event) {
const enteredValue = parseInt(event.target.value, 10);
const range = this.state.range.slice(0);
const breakpointsCount = this.state.breakPointsCount;
if (range.length < breakpointsCount) {
range.push(enteredValue);
}
this.setState({
range: range,
progress: 0,
});
}
render() {
const range = this.state.range;
const breaks = Array.from(Array(this.state.breakPointsCount));
return (
<div className="progress">
[{range.map((item, i) => (
<div key={item}>
<span className="break-point-value">{item}</span>
</div>
))}]
<div className="progress-options">
<label>Change count of break points (up to 10) </label>
<input type="number"
min="0"
max="10"
name="numberInput"
className="app-input"
onChange={this.addBreakPoints.bind(this)}
/>
</div>
<div className="progress-options">
<label>Change a value for each break point </label>
{breaks.map((item, i) => (
<input type="number"
key={`break-${i}`}
className="app-input"
onBlur={this.addBreakPointValue.bind(this)}
/>
))}
</div>
</div>
)
}
}
React.render(<App />, document.getElementById('app'));
You will need to keep track of which input got changed, so you would want to pass some kind of id to the input.
I recommend using a Component composition instead of binding and passing parameters to the inline handler.
You can write a small and simple Input component that all it does is getting a value and id and passing it back up onChange (or onBlur) in your case.
Then your change handler could look something similar to this:
addBreakPointValue = (value, id) => {
this.setState(({range}) => {
const nextRange = [...range];
nextRange[id] = value;
return{
range: nextRange
}
});
}
I wrote a simple example with your code, note that i changed some stuff, like using arrow functions as handlers (class members) so we can take advantage of their lexical context with this instead of binding the functions to the class.
class Input extends React.Component {
onBlur = ({ target }) => {
const { id, onBlur } = this.props;
onBlur(target.value, id);
}
render() {
const { value } = this.props;
return (
<input
type="number"
min="0"
max="10"
value={value}
onBlur={this.onBlur}
/>
)
}
}
class App extends React.Component {
state = {
breakPointsCount: 0,
range: []
}
addBreakPoints = ({ target }) => {
const val = parseInt(target.value, 10);
this.setState({ breakPointsCount: val });
}
addBreakPointValue = (value, id) => {
this.setState(({range}) => {
const nextRange = [...range];
nextRange[id] = value;
return{
range: nextRange
}
});
}
render() {
const { range, breakPointsCount } = this.state;
const breaks = Array.from(Array(breakPointsCount));
return (
<div className="progress">
<div className="progress-bar-wrapper">
<div className="progress-bar" style={{ width: `${this.state.progress}%` }} />
[{range.map((item, i) => (
<div key={item}>
<span className="break-point-value">{item}</span>
</div>
))}]
</div>
<div className="progress-options">
<label>Change count of break points (up to 10) </label>
<input type="number"
min="0"
max="10"
name="numberInput"
className="app-input"
onChange={this.addBreakPoints.bind(this)}
/>
</div>
<div className="progress-options">
<label>Change a value for each break point </label>
{breaks.map((item, i) => (
<Input type="number"
key={i}
id={i}
value={item}
className="app-input"
onBlur={this.addBreakPointValue}
/>
))}
</div>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Here is the link to fixed codepen. I have made two changes.
First in addBreakPoints method i init the range to array of the size of the input initialized with zeroes (for 5 its gonna be [0,0,0,0,0])
addBreakPoints(event) {
this.setState({
breakPointsCount: parseInt(event.target.value, 10),
progress: 0,
range: [...Array(parseInt(event.target.value, 10)).map(()=>0)]
});
}
next in render() method i bind addBreakPointValue to pass the index of the element to be updated.
{breaks.map((item, i) => (
<input type="number"
key={`break-${i}`}
className="app-input"
onBlur={this.addBreakPointValue.bind(this,i)}
/>
))}
and finally in addBreakPointValue i only update the desired element (map function return same value for all indexes except new value for the index that is passed as parameter to addBreakPointValue)
addBreakPointValue(index, event) {
const enteredValue = parseInt(event.target.value, 10);
this.setState((prevState) => ({range : prevState.range.map((r,i) => {
if(i != index) return r
return enteredValue
})}))
}
Hope it is helpful for you.
Edit: Although it now updates values, there is still issues with this code. I will propose a complete solution later.