React adding/removing items to an array - javascript

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.

Related

removing an element from array not working

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>

how to add new input field after click plus icon in React Js

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.

Price filtering in reactjs

I am working on e-commerce React/Redux project, I want to make functionality by which user can display the products according to price slider, I have made two input fields in which user can type min and max price value,This functionality is working on button click, but I want on change event, As the user type second value it filters product onChange,
can anyone help me to sort this issue, Thanks in advance, My code and screenshot is attached below
class PriceInput extends React.Component {
constructor(props) {
super(props);
this.state = {
value: props.values,
};
this.onValueChangeComplete = this.onValueChangeComplete.bind(this);
}
onValueChangeComplete() {
const { onValueChange } = this.props;
onValueChange(this.state.value);
}
render() {
const { currencyCode, limits } = this.props;
const { value } = this.state;
const notChanged = _.isEqual(value, limits);
return (
<div className={styles.wrapper}>
<div className={styles.inputWrapper}>
{I18n.getComponent(
(formattedValue) =>
<input
type="text"
name="min"
className={styles.textInput}
placeholder={formattedValue}
/>,
'filter.price-range-min'
)}
<span className={styles.between}>{I18n.getText('filter.price-aed', {}, 'To')}</span>
{I18n.getComponent(
(formattedValue) =>
<input
type="text"
name="max"
className={styles.textInput}
placeholder={formattedValue}
/>,
'filter.price-range-min'
)}
</div>
</div>
);
}
}
Component in which I have to used the price functionality
case 'price':
childComponent = (
<PriceInput values={facet.data}
limits={facet.data}
currencyCode={this.props.currency.code}
onValueChange={(data) => this.onSearchChange(facet.code, data)}/>
);
break;
It's gonna be something like:
handleMaxChange(event) {
const minValue = '' // I'm assuming you already have max's value saved somewhere
const { value } = event.target
if (minValue && value) {
this.props.onValueChange(/* send values here */)
}
}
render() {
return (
...
<input
type="text"
name="max"
className={styles.textInput}
placeholder={formattedValue}
onChange=={handleMaxChange}
/>
)
}

input filtering in reactjs

I am working on e-commerce React/Redux project, I want to make functionality by which user can display the products according to price filter, I have made two input fields in which user can type min and max price value and can display the products which lie between the price range,
the functionality is working onChange but not displaying the products between the range, it is displaying general products
can anyone help me to sort this issue, Thanks in advance, My code and screenshot is attached below
class PriceInput extends React.Component {
constructor(props) {
super(props);
this.state = {
value: props.values,
};
this.onValueChangeComplete = this.onValueChangeComplete.bind(this);
}
onValueChangeComplete() {
const { onValueChange } = this.props;
onValueChange(this.state.value);
}
render() {
const { currencyCode, limits } = this.props;
const { value } = this.state;
const notChanged = _.isEqual(value, limits);
return (
<div className={styles.wrapper}>
<div className={styles.inputWrapper}>
{I18n.getComponent(
(formattedValue) =>
<input
type="text"
name="min"
className={styles.textInput}
placeholder={formattedValue}
/>,
'filter.price-range-min'
)}
<span className={styles.between}>{I18n.getText('filter.price-aed', {}, 'To')}</span>
{I18n.getComponent(
(formattedValue) =>
<input
type="text"
name="max"
className={styles.textInput}
placeholder={formattedValue}
onChange={this.onValueChangeComplete}
/>,
'filter.price-range-min'
)}
</div>
</div>
);
}
}
Component in which I have to used the price functionality
case 'price':
childComponent = (
<PriceInput values={facet.data}
limits={facet.data}
currencyCode={this.props.currency.code}
onValueChange={(data) => this.onSearchChange(facet.code, data)}/>
);
break;
This is not really a fix (I don't think) but maybe it can bring you closer to a solution. I made some edits to your code and placed comments where I made changes.
class PriceInput extends React.Component {
constructor(props) {
super(props);
this.state = {
// NOTE: I don't know how props.values looks. maybe this is wrong
min: props.values.min,
max: props.values.max
};
this.onValueChangeComplete = this.onValueChangeComplete.bind(this);
}
onValueChangeComplete(minOrMax, newValue) {
const { onValueChange } = this.props;
this.setState(
{[minOrMax]: newValue}, // update the property "min" or "max" with the new value
() => onValueChange(this.state) // when the state change is done, send both min and max to onValueChange
);
// onValueChange(this.state.value);
}
render() {
// not sure what "limits" are used for
// maybe you want to use an input with type="number" and
// use the attributes "min" and "max" ? https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number
const { currencyCode, limits } = this.props;
const { min, max } = this.state; // this is new.
const notChanged = _.isEqual(value, limits);
return (
<div className={styles.wrapper}>
<div className={styles.inputWrapper}>
{I18n.getComponent(
(formattedValue) =>
<input
value={ min } // this is new
type="text"
name="min"
className={styles.textInput}
placeholder={formattedValue}
onChange={ event => this.onValueChangeComplete('min', event.target.value) } // this was missing before
/>,
'filter.price-range-min'
)}
<span className={styles.between}>{I18n.getText('filter.price-aed', {}, 'To')}</span>
{I18n.getComponent(
(formattedValue) =>
<input
value={ max } // this is new
type="text"
name="max"
className={styles.textInput}
placeholder={formattedValue}
onChange={ event => this.onValueChangeComplete('max', event.target.value )}
/>,
'filter.price-range-min'
)}
</div>
</div>
);
}
}

Looping Through Text Inputs in React

I'm building an app where I want the user to specify a number of text fields and then render this amount of fields dynamically. I'm having trouble setting up the state so that it is unique for each field. Here is the code segment:
var fieldsArray = [];
for(var i = 0; i <= this.props.numToShow; i ++){
fieldsArray.push(
<div>
<label>
<div className="label">{i}</div>
<input type='text' value={this.state.value} name={this.state.value} onChange={this.handleChange} />
</label>
</div>
);
}
return (
<div className = 'inputs'>
{fieldsArray}
</div>
);
Currently, when I change one of the fields, all the other fields update with that unique fields state. Here is the handleChange function that sets the state:
handleChange: function(e){
this.setState({
value: e.target.value,
});
}
Is it possible to initialize the state as an array and keep track of the inputs that way? Or is there a better way to do this?
Keeping an array of values in state would work fine. You'll just have to make sure you're passing the index of the input so you know what to update. bind helps with this:
class YourComponent extends React.Component {
constructor(props) {
super(props);
this.state = { values: [] };
}
handleChange(i, e) {
this.setState({
values: { ...this.state.values, [i]: e.target.value }
});
}
render() {
var fieldsArray = [];
for (var i = 0; i <= this.props.numToShow; i++) {
fieldsArray.push(
<div>
<label>
<div className="label">{i}</div>
<input
type='text'
value={this.state.values[i]}
name={this.state.values[i]}
onChange={this.handleChange.bind(this, i)} />
</label>
</div>
);
}
return (
<div className = 'inputs'>
{fieldsArray}
</div>
);
}
}
onChange={(e) => {
var newPresetList = [...presetList]
newPresetList.map((_item) => {
if (_item.id === item.id) {
_item.preset_name = e.target.value
return
}
})
setPresetList(newPresetList)
}}

Categories