Easiest way, using an arrow function:
class InputsList extends React.Component {
render() {
const items = this.props.fields.map((field, i) => (
<YourInputComponent onChange={
(e) => this.props.onChange(i, e.target.value) } />
);
return (<div>{ items }</div>);
}
}
The problem with this approach is that the arrow function inside onChange will reallocate a new function every time render is called. That could cause performance issues due to:
- Pure Components will render again, as the old (arrow) function and the new (arrow) function will be different, even thought both are calling the exact same fuction.
- Garbage collector overhead to get rid of those old functions.
A better approach would be to pass any relevant data down to YourInputComponent, which will be responsible for calling the onChange with the relevant index/id that allows you to identify which input triggered the event.
class InputsList extends React.Component {
render() {
const items = this.props.fields.map((field, i) => (
<YourInputComponent index={ i } onChange={ this.props.onChange } />
);
return (<div>{ items }</div>);
}
}
In YourInputComponent you will have something like this:
this.props.onChange(this.props.index, e.target.value);
Now the function that you pass to onChange will always be the same, as long as it doesn't change in the parent component, so YourInputComponent won't be rerendered.
Here you have a working example of the first method:
class List extends React.PureComponent {
render() {
const { values, length } = this.props;
const items = [];
for (let i = 0; i < length; ++i) {
const value = values[i];
items.push(<li key={ `${ value }-${ i }` } className="list__item">{ value }</li>);
}
return (<ul>{ items }</ul>);
}
}
class InputsList extends React.PureComponent {
render() {
const { name, label, totalInputs, onChange } = this.props;
const inputs = [];
for (let i = 0; i < totalInputs; ++i) {
inputs.push(
<li key={ i }>
<label className="inputs__label">
{ `${ label } ${ i + 1 }` }
<input
className="inputs__input"
type='text'
name={ `${ name }-${ i }` }
onInput={ (e) => onChange(i, e.target.value) } />
</label>
</li>
);
}
return (<ul>{ inputs }</ul>);
}
}
class RadioTabs extends React.PureComponent {
render() {
const { name, value, options, onChange } = this.props;
const radios = options.map(option => (
<li key={ option }>
<label className="radioTabs__label">
<input
className="radioTabs__input"
type="radio"
value={ option }
checked={ value === option }
name={ name }
onChange={ onChange } />
<span className="radioTabs__text">{ option }</span>
</label>
</li>
));
return(
<ul className="radioTabs__base">
{ radios }
</ul>
);
}
}
class App extends React.Component {
static options = [1, 2, 3, 4, 5, 6, 7];
constructor(props) {
super(props);
this.state = {
totalInputs: App.options[0],
values: [],
};
}
onTotalInputsChange = (e) => {
this.setState({
totalInputs: parseInt(e.target.value),
values: this.state.values,
});
};
onInputsChange = (index, value) => {
const values = [ ...this.state.values ];
values[index] = value;
this.setState({
totalInputs: this.state.totalInputs,
values,
});
};
render() {
const { totalInputs, values } = this.state;
return(
<div className="app">
<div className="header">
<RadioTabs
name="days"
value={ totalInputs }
options={ App.options }
onChange={ this.onTotalInputsChange } />
</div>
<div className="columns">
<div className="left">
<InputsList
name="values"
totalInputs={ totalInputs }
label="Day"
onChange={ this.onInputsChange } />
</div>
<div className="right">
<List values={ values } length={ totalInputs }/>
</div>
</div>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
body {
font-family: monospace;
margin: 0;
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
.app {
height: 100vh;
display: flex;
flex-direction: column;
}
.header {
padding: 4px;
border-bottom: 2px solid black;
flex: 0 0 auto;
}
.columns {
display: flex;
flex: 1 1 auto;
}
.left,
.right {
flex: 1 0 0;
overflow-y: scroll;
padding: 4px;
}
.left {
border-right: 2px solid black;
}
/* RADIO TABS */
.radioTabs__base {
display: flex;
align-items: center;
}
.radioTabs__label {
display: block;
padding: 4px 0;
cursor: pointer;
border-radius: 2px;
min-height: 27px;
min-width: 35px;
display: flex;
align-items: center;
justify-content: center;
}
.radioTabs__label:hover {
background: #EEE;
}
.radioTabs__input {
display: none;
}
.radioTabs__text {
display: block;
padding: 2px 4px 0;
border-bottom: 2px solid transparent;
}
.radioTabs__input:checked + .radioTabs__text {
border-bottom-color: black;
}
/* INPUTS LIST */
.inputs__label {
display: block;
padding: 4px 8px;
cursor: pointer;
border-radius: 2px;
display: flex;
align-items: center;
}
.inputs__label:hover {
background: #EEE;
}
.inputs__input {
border: 2px solid black;
padding: 4px 8px;
margin: 0 0 0 8px;
font-family: monospace;
flex: 1 0 auto;
}
/* LIST */
.list__item {
border-bottom: 2px solid black;
line-height: 33px;
height: 33px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
<div id="app"></div>
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>