Getting a value of inputs populated Dynamically React.js - javascript

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.

Related

Material UI checkbox component not working

I am using material UI checkbox components inside a react component. I can’t get the basic checked/unchecked function working although I believe I did and checked everything. Anyone care to help? here’s the code:
class SplitEqually extends React.Component {
constructor(props) {
super(props);
let checked = this.props.contributors.map((contributor) => contributor.id)
this.state = {
checkedContributors: this.props.contributors,
checkedId: checked,
};
}
handleChange = (e, contri) => {
let checkedId = this.state.checkedId.includes(contri.id)
? this.state.checkedId.filter((id) => id !== contri.id)
: [...this.state.checkedId, contri.id];
console.log(checkedId)
let checkedContributors = this.state.checkedContributors.filter((contri) =>
checkedId.includes(contri.id)
);
this.setState(checkedId);
};
render() {
const { classes, contributors } = this.props;
const { checkedContributors, checkedId } = this.state;
return (
<div className={classes.splitUnequally}>
{contributors.map((contributor, i) => {
let { id, name } = contributor;
console.log(checkedId.includes(id));
return (
<div className={classes.list} key={id}>
<div className={classes.avatar}></div>
<FormControlLabel
labelPlacement="start"
control={
<Checkbox
onChange={(e) => this.handleChange(e, contributor)}
name={name}
checked={checkedId.includes(id)}
/>
}
label={name}
/>
</div>
);
})}
<br></br>
</div>
);
}
}
checkedContributors state variable is an array of object, each object defining one user with a unique id property.
CheckedId state variable is an array which contains unique id of only those users who are checked.
I was using the wrong syntax for setting the state. It should have been this.setState({checkedId}); and not this.setState(checkedId);. Correcting this resolved the issue

In React compare two array of object then check checkbox accordingly

In React I'm creating a multiple choice questionnaire. Checkboxes are generated with the possible answers. When the user ticks the answers and reloads the page, the chosen answers' checkboxes do not retained their checked state.
The questions and answers are fetched from database as an array of objects on 1st load. The user can tick multiple checkboxes for a question. A 2nd array is created that includes all the multiple answers that user has chosen and sent to database has objects. On reload, this 2nd array is added to the state of the component as well as the 1st array.
Component
const Checkbox = ({ id, name, options, onChange }) => {
return (
<div className="checkbox-group">
{options.map((option, index) => {
<div key={index}>
<label htmlFor={id}>
<input
type="checkbox"
name={name}
id={id}
value={option}
onChange={onChange}
/>
{option}
</label>
</div>
}
</div>
);
}
class Form extends Component {
constructor(props) {
super(props);
this.state = {
questionnaire: [],
answeredQuestions: [],
formData: {},
};
this.handleChange = this.handleChange.bind(this);
}
async componentDidMount() {
// it doesn't matter how I fetch the data, could have been axios, etc...
let questionnaire = await fetch(questionnaireUrl);
let answeredQuestions = await fetch(answeredQuestionsUrl);
this.setState({ questionnaire, answeredQuestions });
}
render() {
return (
<div className="questionnaire-panel">
<h1>Quiz</h1>
{this.state.questionnaire.map((question, index) => {
return (
<div key={index}>
<Checkbox
options={questions.answers}
checked={// this where I'm stuck on what to do}
name="the-quiz"
id={`the-quiz_num_${index + 1}`}
onChange={this.handleChange}
/>
</div>
)
})}
</div>
);
}
handleChange(event) {
let target = event.target;
let value = target.value;
let name = target.name;
let chosenAnwersArray = [];
let chosenAnswer = {
answer: value,
checked: true,
};
if (this.state.questionnaire.includes(chosenAnswer)) {
newChosenAnwersArray = this.state.questionnaire.filter(q => {
return q.answer !== chosenAnswer.answer;
});
} else {
newChosenAnwersArray = [...newChosenAnwersArray, chosenAnswer];
}
this.setState(prevState => ({
formData: {
[name]: value,
},
answeredQuestions: newChosenAnwersArray
}));
}
}
I want to compare these 2 arrays that are in the this.state, that if the answers in the array2 are in array1 then check the corresponding checkboxes. Is there is a better way, please teach me!

Component not updating on Map (dictionary) state change using hooks

I have a child component that is supposed to display names based on a visibility filter with checkboxes. I use a dictionary to keep track of the checked state for each name. However, when I update the dictionary the child does not update.
Here is an example: https://codesandbox.io/s/8k39xmxl52
These are the components:
const App = () => {
const [names, setNames] = useState(seedNames);
const [hidden, setHidden] = useState(new Map());
const handleHidden = e => {
const name = e.target.name;
const hidden = e.target.checked;
setHidden(hidden.set(name, hidden));
};
return (
<div className="App">
<VisibilityFilter
names={names}
hidden={hidden}
handleHidden={handleHidden}
/>
<DisplayNames names={names} hidden={hidden} />
</div>
);
};
const VisibilityFilter = ({ names, hidden, handleHidden }) => {
return (
<div>
{names.map(name => (
<div key={name}>
<input
type="checkbox"
name={name}
checked={hidden.get(name)}
onChange={handleHidden}
defaultChecked
/>
<span>{name}</span>
</div>
))}
</div>
);
};
const DisplayNames = ({ names, hidden }) => {
const visibleNames = names.filter(name => !hidden.get(name));
return (
<div>
{visibleNames.map(name => (
<div key={name}>{name}</div>
))}
</div>
);
};
The use of immutable state is idiomatic to React, it's expected that simple types like plain objects and arrays are used.
Map set returns the same map, while useState supports only immutable states. In case setter function is called with the same state, a component is not updated:
setHidden(hidden.set(name, hidden))
The state should be kept immutable:
setHidden(new Map(hidden.set(name, hidden)))
This may introduce some overhead and defy possible benefits of ES6 Map. Unless Map features like strict item order and non-string keys are in demand, it's preferable to use plain object for the same purpose.

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.

Accessing input value from stateless child component in parent through refs

I'm creating a program to track store inventory. I have an array of item names (strings) that I map through to generate a component that renders a heading for each item along with a corresponding input field:
function Inventory(props){
let items = ['milk', 'bread', 'butter'],
itemInput = items.map((value,index) => {
return(
<div key={index}>
<h3>{value}</h3>
<input type={'text'} />
</div>
)
})
return(
<div>
{itemInput}
</div>
)
};
Screenshot of output
How can I access both the input value as well as well as its corresponding heading? For example, if I type 5 within the input for milk, I want to be able to access both 5 and milk.
I've tried using refs (which winds up referencing only the last array item), event and this to no avail. Any suggestions will be much appreciated.
You are using a functional component which doesn't have a state or refs. You have two options, Either set the value as props passed down from the parent or make it a stateful component.
Stateless components must be dumb component used specifically for rendering and all logic must reside in the stateful parent component.
According to the docs
You may not use the ref attribute on functional components because
they don't have instances.You should convert the component to a class
if you need a ref to it, just like you do when you need lifecycle
methods or state
In first case
function Inventory(props){
let items = ['milk', 'bread', 'butter'],
itemInput = items.map((val,index) => {
return(
<div key={index}>
<h3>{val}</h3>
<input type={'text'} value={props.childInput[val] || '' } onChange={(e) => props.handleChange(e, val)}/>
</div>
)
})
return(
<div>
{itemInput}
</div>
)
};
And then the parent will have the logic like
<Inventory handleChange={this.handleChange} childInput={this.state.childInputVal}/>
handleChange = (e, key) => {
var childInputVal = {...this.state.childInputVal}
childInputVal[key] = e.target.value
this.setState({childInputVal})
}
state = {
childInputVal: {}
}
The other option is to make this component itself a stateful component
class Inventory extends React.Component {
state= {
inputValues: {}
}
handleChange = (e, val) => {
handleChange = (e, key) => {
var childInputVal = {...this.state.inputValues}
inputValues[key] = e.target.value
this.setState({inputValues})
}
render() {
let items = ['milk', 'bread', 'butter'],
itemInput = items.map((val,index) => {
return(
<div key={index}>
<h3>{val}</h3>
<input type={'text'} value={this.state.inputValues[val] || '' } onChange={(e) => this.handleChange(e, val)}/>
</div>
)
}
return(
<div>
{itemInput}
</div>
)
}
It is possible to use the onChange handler for this:
<input type="text" onChange={e => this.setState({ [value]: e.target.value })} />
The state now will look something like this:
{
milk: 5,
bread: 2,
butter: 10
}

Categories