Updating state for array ReactJS? - javascript

Ok so here's my code:
var uuid = require("uuid-v4");
// Generate a new UUID
var myUUID = uuid();
// Validate a UUID as proper V4 format
uuid.isUUID(myUUID); // true
var questionNum = 0;
class App extends Component {
constructor(props) {
super(props);
this.state = {
key: uuid(),
title: "",
author: "",
questions: [],
answers: []
};
this.handleChange = this.handleChange.bind(this);
this.addQuestion = this.addQuestion.bind(this);
this.removeItem = this.removeItem.bind(this)
}
componentDidMount() {
// componentDidMount() is a React lifecycle method
this.addQuestion();
}
handleChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
removeItem (index) {
questionNum--;
this.setState(({ questions }) => {
const mQuestions = [ ...questions ]
mQuestions.splice(index, 1)
return { questions: mQuestions }
})
this.setState(({ answers }) => {
const mAnswers = [ ...answers]
mAnswers.splice(index, 4)
return { answers: mAnswers}
})
console.log(
"answers",
this.state.answers,
"questions",
this.state.questions,
questionNum,
this.state.title,
this.state.author
);
}
addQuestion() {
questionNum++;
this.setState(previousState => {
const questions = [
...previousState.questions,
<input
type="text"
value={this.state.questions}
onChange={this.handleChange}
name="question"
key={uuid()}
/>
];
const answers = [
...previousState.answers,
];
for (var i = 0; i < 4; i++) {
answers.push(
<input
type="text"
onChange={this.handleChange}
name={uuid()}
/>
);
}
return { questions, answers };
});
console.log(
"answers",
this.state.answers,
"questions",
this.state.questions,
questionNum,
this.state.title,
this.state.author
);
}
render() {
return (
<div className="App">
<div>
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Quiz Form 3.0</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
<div className="formDiv">
<form>
<div className="Intro">
Give your Quiz a title:{" "}
<input
type="text"
value={this.state.title}
onChange={this.handleChange}
name="title"
/>
<br />
Who's the Author?{" "}
<input
type="text"
value={this.state.author}
onChange={this.handleChange}
name="author"
/>
<br />
<br />
</div>
<div className="questions">
<div className="questions">
Now let's add some questions... <br />
<ol>
{this.state.questions.map((question, index) => {
return (
<li>
<div key={uuid()}>
Question
{question}<br />
<button onClick={ () => this.removeItem(index) }>
Remove Question
</button>
Answer Choices<br />
{Array.from({ length: 4 }, () => (
<div>
<input type="checkbox" />
<input type="text" onChange={this.handleChange} />
</div>
))}
</div>
</li>
);
})}
</ol>
</div>
{
// This is what it would look like for the structure
// I proposed earlier.
// this.state.questions.map((question) {
// return (
// <div>{question.quesion}</div>
// {
// question.answers.map((answer) => {
// return (<div>{answer}</div>);
// })
// }
// );
// })
// This would output all questions and answers.
}
</div>
</form>
<button id="addQuestionButton" onClick={this.addQuestion}>Add Question</button>
</div>
</div>
);
}
}
export default App;
I am having trouble with updating my state for my answers and questions states/props. I don't quite understand what'g happening with them to be honest. The addQuestion function is called when the Add Question button is pressed and that adds 4 objects (each answer choice, 4 to 1 question) and one question. When the removeQuestion function is called (when Remove Question button is pressed) that question, along with its answer choices is removed from their corresponding arrays. I notice that it doesn't matter which answer choice input or question input I type in, the question inputs all update the same (new and blank) state, and the answer choice inputs do the same thing. I made some edits, so I don't think that it's possible to type in them right now (because when I did make them typable the remove and add question buttons didn't work properly). If anyone has an explanation for how to make it so that the array states update correctly that would be super helpful. I am really new to this so if you have any other suggestions that would be cool as well. Thanks!

Related

Why my form input is typing per 1 letter and then stop if passing props

I know this before but I forgot how did I do it..so basically let say I have
const [showItem,setShowItem] = useState({})
const [updateName,setUpdateName] = useState('')
and then I have a props function that will do something like this...this props is callable for array of items and I want to do this to make it more cleaner and reuseable.
<ItemsEdit items={showItem} setUpdateName_= { setUpdateName } updateName = { updateName } ></ItemsEdit>
Now as you see, when I'm trying to pass my setUpdateName_ and updatename.
UPDATE
This is the map for my items that will call the <ItemsEdit/> for specific id only in my buttons. (but this not affect anything in form)
{nfts.map((nft,i) => {
return(
<div
className="items"
key={nft.id}
>
{showItem.id == nft.id ? <>
<form onSubmit={handleSubmit}>
<ItemsEdit
items={showItem}
setUpdateName= { setUpdateName }
updateName = { updateName }
/>
</form>
</> : <>
<Items items={nft}></Items>
</>}
</div>
)
})}
and here is the <ItemsEdit/>
so for every key that I press it will lose the focus in input but when I used autoFocus = "autoFocus" in the input text it will works but the only thing is that it will the same text in other items..so its not the best idea for me.
const ItemsEdit = ({items,setUpdateName,updateName}) => {
return (
<>
<input
id='name'
type="text"
key="text"
// autoFocus="autoFocus"
value = {updateName}
placeholder="NFT name"
onChange={ e => setUpdateName( e.target.value )}
></input>
<p>{items.id}</p>
<img src={items.data.img} alt="" />
<div className="buttons">
<button
type='submit'>
Update
</button>
<button type='submit' className='left'
onClick={
() => {
setShowItem({})}
}>
<ion-icon name="arrow-back-circle-outline"></ion-icon>
</button>
</div>
</>
)
}
I now have an answer but this kinda nasty for me
so for the <ItemsEdit/> I would call it something like this
{ItemsEdit(
{items:showItem,
setUpdateName:setUpdateName,
updateName:updateName}
)}
and just remove the return of and change the { } into ( )just like this
const ItemsEdit = ({items,setUpdateName,updateName}) => (
)

How to implement a check all button for radio buttons, using React Hooks?

Don't get this confused with checking each radio button I have on the page. I want to implement a check all button that sets the value of a nested object state equal to a certain value. I am storing each question in a nested state. Ex.
formQuestions({
kitchen: [question,question2,question3],
living: [question,question2,question3]
})
Four radio buttons are being made for each question. Now one radio button can only be selected at once. Each radio button has its' own value. Ex. `"Good", "Fair", "Poor", "N/A".
When a radio button is selected a state is generated dynamically for that section and question. Ex.
formAnswers({
kitchen: {
question: "Good"
question2: "Poor"
}
})
The goal here is the button that I want to create that checks only one value for each question Ex. clicks button question: "Good", question2: "Good" etc..
For me to set the state of a dynamic value I would need the "Section name" lets call it Name and the "Question" we'll call it question. That would give me access to the value like so formAnswers[Name][question]: value
I am trying to set that state from a component called SectionHeader. These contain the buttons.
SectionHeader.js
import { FormAnswersContext, FormQuestionsContext } from "../../Store";
function SectionHeader({ title, name }) {
const [formAnswers, setFormAnswers] = useContext(FormAnswersContext);
const [formQuestions, setFormQuestions] = useContext(FormQuestionsContext);
return (
<div>
<h1 className={styles["Header"]}>{title}</h1>
<div className={styles["MarkAllWrapper"]}>
<button className={styles["MarkAll"]}>
Mark all items as "Good" in this section
</button>
<br />
<button className={styles["MarkAll"]}>
Mark all items as "N/A" in this section
</button>
</div>
</div>
);
}
The parent of Section Header and the rest of the form code excluding the child radio buttons which I have explained, are in another component LivingRoom.js
LivingRoom.js
import { FormQuestionsContext, FormAnswersContext } from "../../Store";
function LivingRoomForm({ Name }) {
const [expanded, setExpanded] = useState(false);
const [formQuestions, setFormQuestions] = useContext(FormQuestionsContext);
const [formAnswers, setFormAnswers] = useContext(FormAnswersContext);
const array = formQuestions.living;
const onChange = (e, name) => {
const { value } = e.target;
setFormAnswers((state) => ({
...state,
[Name]: { ...state[Name], [name]: value },
}));
};
const handleOpen = () => {
setExpanded(!expanded);
};
return (
<div>
<Button
className={styles["CollapseBtn"]}
onClick={handleOpen}
style={{ marginBottom: "1rem", width: "100%" }}
>
<p>LIVING ROOM INSPECTION</p>
<FontAwesome
className="super-crazy-colors"
name="angle-up"
rotate={expanded ? null : 180}
size="lg"
style={{
marginTop: "5px",
textShadow: "0 1px 0 rgba(0, 0, 0, 0.1)",
}}
/>
</Button>
<Collapse className={styles["Collapse"]} isOpen={expanded}>
<Card>
<CardBody>
{array ? (
<div>
<SectionHeader title="Living Room Inspection" name={Name} />
<div
className={styles["LivingRoomFormWrapper"]}
id="living-room-form"
>
{array.map((question, index) => {
const selected =
formAnswers[Name] && formAnswers[Name][question]
? formAnswers[Name][question]
: "";
return (
<div className={styles["CheckboxWrapper"]} key={index}>
<h5>{question}</h5>
<Ratings
section={Name}
question={question}
onChange={onChange}
selected={selected}
/>
</div>
);
})}
</div>
<br />
<ImageUploader name="living" title={"Living Room"} />
</div>
) : (
<div></div>
)}
</CardBody>
</Card>
</Collapse>
</div>
);
}
If there is anything I am missing please let me know, I would be happy to share it. Cheers
Edit: for anyone that needs the radio buttons component.
Ratings.js
import React from "react";
import { FormGroup, CustomInput } from "reactstrap";
function Ratings({ selected, section, question, onChange }) {
return (
<div>
<FormGroup>
<div>
<CustomInput
checked={selected === "Good"}
onChange={(e) => onChange(e, question)}
type="radio"
id={`${section}_${question}_Good`}
value="Good"
label="Good"
/>
<CustomInput
checked={selected === "Fair"}
onChange={(e) => onChange(e, question)}
type="radio"
id={`${section}_${question}_Fair`}
value="Fair"
label="Fair"
/>
<CustomInput
checked={selected === "Poor"}
onChange={(e) => onChange(e, question)}
type="radio"
id={`${section}_${question}_Poor`}
value="Poor"
label="Poor"
/>
<CustomInput
checked={selected === "N/A"}
onChange={(e) => onChange(e, question)}
type="radio"
id={`${section}_${question}_NA`}
value="N/A"
label="N/A"
/>
</div>
</FormGroup>
</div>
);
}
I do not completely understand your question, I am sorry but I think this will help you.
Here is an implementation of radio buttons using react -
class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
handleChange = e => {
const { name, value } = e.target;
this.setState({
[name]: value
});
};
render() {
return (
<div className="radio-buttons">
Windows
<input
id="windows"
value="windows"
name="platform"
type="radio"
onChange={this.handleChange}
/>
Mac
<input
id="mac"
value="mac"
name="platform"
type="radio"
onChange={this.handleChange}
/>
Linux
<input
id="linux"
value="linux"
name="platform"
type="radio"
onChange={this.handleChange}
/>
</div>
);
}
}
After a few attempts, I was able to figure out the solution to this issue.
The key here was to figure out a way to get gather each question so that it may be used as a key when setting the state. As my questions were stored in a ContextAPI, I was able to pull them out like so...
this may not be the best solution however it worked for me.
const setStateGood = () => {
formQuestions[name].map((question) => {
setFormAnswers((state) => ({
...state,
[name]: { ...state[name], [question]: "Good" },
}));
});
};
const setStateNA = () => {
formQuestions[name].map((question) => {
setFormAnswers((state) => ({
...state,
[name]: { ...state[name], [question]: "N/A" },
}));
});
};
I was able to map through each question since the name is being passed through props is a key inside the actual object, formQuestions[name]. Because i'm mapping through each one I can set that question as a key and return the new state for each question to whatever I would like.
However, if I was to create an onClick={setState('Good')}, React didn't like that and it created an infinite loop. I will look for more solutions and update this post if I find one.

managing state in react with dynamically generated form

I have a component that fetches a json list of questions from an api. Then there's 5 radio buttons for every question for answering (from "strongly disagree" to "strongly agree").
The questions are fetched in componentDidMount(), stored in this.state.questions, and mapped to questionComponents in render(). The components (and radio buttons) are identified by key.
I need to store the answers as an array in the state. This obviously has to happen in handleChange, but I've no idea how to do this. I'm pretty new to react so there's probably an easier way to do this than what I'm doing now.
Here's App.js
import React from 'react';
import Question from './Question';
class App extends React.Component {
constructor() {
super()
this.state = {
questions: [],
answers: []
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
const {name, value, type, checked, key} = event.target
this.setState(prevState => {
//
}
}
componentDidMount() {
fetch("http://localhost:7777/api/questions")
.then(response => response.json())
.then(data => {
this.setState({
questions: data
})
})
}
render () {
const questionComponents = this.state.questions.map(question =>
<Question key={question.id} question={question.question} handleChange = {this.handleChange} />)
return (
<div>
<h1> Questions!</h1>
{questionComponents}
</div>
)
}
}
export default App;
And Question.js
import React from "react"
function Question(props) {
return (
<div className="question">
<p>{props.question}</p>
<label>strongly disagree</label>
<input
type="radio"
name={props.key}
key={props.key}
value="1"
onChange={props.handleChange}>
</input>
<label> disagree</label>
<input
type="radio"
name={props.key}
key={props.key}
value="2"
onChange={props.handleChange}>
</input>
<label>no opinion</label>
<input
type="radio"
name={props.key}
key={props.key}
value="3"
onChange={props.handleChange}>
</input>
<label> agree</label>
<input
type="radio"
name={props.key}
key={props.key}
value="4"
onChange={props.handleChange}>
</input>
<label>strongly agree</label>
<input
type="radio"
name={props.key}
key={props.key}
value="5"
onChange={props.handleChange}>
</input>
</div>
)
}
export default Question
Here's a working example for you:
class App extends React.Component {
constructor() {
super()
this.state = {
questions: [
{
id: '123',
question: 'What?'
},
{
id: '1234',
question: 'When?'
}
],
answers: {}
}
}
handleChange = (event) => { // using an arrow function instead of binding in the constructor
const { name, value } = event.target;
// this will add / update a key-value pair depending if it exists or not
this.setState(state => state.answers[name] = value)
}
componentDidMount() {
// import your data
}
render() {
console.log(this.state.answers)
const { questions } = this.state;
return (
<div>
<h3>Questions</h3>
{questions.map(question => {
return <Question key={question.id} name={question.id} question={question.question} handleChange={e => this.handleChange(e)} />
})}
</div>
);
}
}
function Question(props) {
// create a label array to loop over instead of typing all input fields
const labels = ['strongly disagree', 'disagree', 'no opinion', 'agree', 'strongly agree']
return (
<div className="question">
<p>{props.question}</p>
<div className="inputs">
{labels.map((label, idx) => {
return (
<div key={props.name}>
<label key={label}>{label}</label>
<input
type="radio"
name={props.name}
value={idx + 1}
onChange={event => props.handleChange(event)}
/>
</div>
)
})}
</div>
</div>
)
}
The answers are stored in an object instead of an array as it's easier to manage. You can also use an array but updating it is a bit more difficult as you have to loop over all items to find the one you want to update.
I didn't know what your question object looks like so I improvised.
Note that the key attribute is used by React to identify elements returned from a map function. Don't use it otherwise.
I hope this helps and good luck!

React Form: How to add error message that disappear if the input was typed in

I already built the form in React and it shows the input fields in red borders that'll change to regular borders once someone types it in. I used this example from this React form article link So everything is working except I wanted to add the error message under the input field that displays "Please fill in the blank field" that will disappear once someone starts typing in the field. How do I do this?
Here's my code in Form.js:
import React, { Component } from 'react';
import FormField from './FormFieldBox';
function validate(name, isin) {
// true means invalid, so our conditions got reversed
return {
name: name.length === 0,
isin: isin.length === 0
};
}
export default class PopupForm extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
isin: '',
country: '',
errormessage: ''
}
}
updateInput = (e) =>{
this.setState({[e.target.name]: e.target.value})
}
closePopupSubmit = (e) => {
if (!this.canBeSubmitted()) {
e.preventDefault();
}
let security = { //1.gather security data from form submit
name: this.state.name,
isin: this.state.isin,
country: this.state.country
}
this.props.submitPopup(security); //2.closePopup function, add security data
}
canBeSubmitted() {
const errors = validate(this.state.name, this.state.isin);
const isDisabled = Object.keys(errors).some(x => errors[x]);
return !isDisabled;
}
cancelPopupSubmit = (e) => {
e.preventDefault()
this.props.cancelPopup();
}
render() {
const errors = validate(this.state.name, this.state.isin);
const isDisabled = Object.keys(errors).some(x => errors[x]);
return (
<div className='popup'>
<div className='popup-inner'>
<form onSubmit={this.closePopupSubmit}>
<FormField onChange={this.updateInput} className={errors.name ? "input error" : "input"} label="Name" type="text" name="name" value={this.state.name} />
<FormField onChange={this.updateInput} className={errors.isin ? "input error" : "input"} label="ISIN" type="text" name="isin" value={this.state.isin} />
<FormField onChange={this.updateInput} label="Country" type="text" name="country" value={this.state.country} />
<button type="button" onClick={this.cancelPopupSubmit} className="button">Cancel</button>
<button type="submit" className="button" disabled={isDisabled}>Submit</button>
</form>
</div>
</div>
)
}
}
And my component FormField.js
import React from "react";
const FormBox = props => {
return (
<div className="field">
<label className="label">{props.label}</label>
<div className="control">
<input onChange={props.onChange}
className={props.className}
type={props.type}
name={props.name}
value={props.value}
placeholder={props.placeholder} />
{/* {props.errormessage} */}
</div>
</div>
)
}
export default FormBox;
const FormBox = props => {
return (
<div className="field">
<label className="label">{props.label}</label>
<div className="control">
<input onChange={props.onChange}
className={props.className}
type={props.type}
name={props.name}
value={props.value}
placeholder={props.placeholder} />
</div>
{Boolean(props.value.length) || (
<div className="err-msg">
Please fill in the blank field
</div>
)}
</div>
)
}
There are two ways you can achieve this
First : oninvalid attribute in HTML5 and calling a custom function on that.
Second : along with each element name object in state have a length attribute. In validation function you can check for the length and throw a custom error that you want to display.

React JS - Store radio button values into array

I need some help regarding storing radio button checked values into an array, I tried but I could'nt make anything work, here is my code
so I want to transfer and store the checked radio buttons into an array
I don't know what to do ..
My code:
class Question extends Component {
constructor() {
super();
this.state = { val: "" };
this.onInput = this.onInput.bind(this);
}
//this.setState = {answers: {}};
onInput(e) {
this.setState({ val: e.target.value });
console.log(this.state.data);
}
render() {
var that = this; // Funny way to be able to access this from inside our iterator() function below.
var iterator = this.props.questionChoices.map((choice, i) => {
return (
<div>
<label className="container">
<input
className="input"
key={i}
type={that.props.questionType}
name={that.props.questionID}
value={choice}
onChange={that.onInput}
/>
{choice}
<span className="checkmark" />
</label>
</div>
);
});
return (
<div className="row">
<div className="form">
<Paper zDepth={1}>
<div className="h3">
<h3 className="h3outter">
{this.props.questionID} - {this.props.questionText}
</h3>
<p className="h3inner">{iterator}</p>
</div>
</Paper>
</div>
</div>
);
}
}
OK. So I fiddled around with your code a little bit and came up with this. In the render it iterates over the questions but also (the bit you were missing) iterates over the radio button choices too.
The important part is in onInput where we take the existing state and add the new radio button value to it. And if the question has already been answered we remove the exising answer, and replace it with the new one.
class Question extends React.Component {
constructor() {
super();
this.state = { values: [] };
this.onInput = this.onInput.bind(this);
this.buildRadioButtons = this.buildRadioButtons.bind(this);
}
onInput(e) {
const id = e.target.name;
const answer = { id, answer: e.target.value };
let answers;
if (this.state.answers.some(answer => answer.id === id)) {
answers = [...this.state.answers.filter(answer => answer.id !== id), answer];
} else {
answers = [...this.state.answers, answer];
}
this.setState({ answers }, () => console.log(this.state.answers));
}
buildRadioButtons(arr, type, id) {
return arr.map((choice, i) => {
return (
<div key={i}>
<input
type={type}
name={id}
value={choice}
onChange={this.onInput}
/>
<label>{choice}</label>
</div>
)
})
}
render() {
var iterator = this.props.questionChoices.map((question, i) => {
const { choices, questionType, questionID, questionText } = question;
return (
<div key={i}>
<h3>{questionText}</h3>
{this.buildRadioButtons(choices, questionType, questionID)}
</div>
);
});
return (
<div className="row">
<div className="form">
<div >
<div className="h3">
{iterator}
</div>
</div>
</div>
</div>
);
}
}
const questionChoices = [
{ "questionID": 3, "questionType": "radio", "questionText": "L'unité du courant électrique est", "choices": ["L’Ampère", "Le Volt", "Le Watt"], "answer": "L’Ampère" },
{ "questionID": 2, "questionType": "radio", "questionText": "What is your name?", "choices": ["Dave", "Nizar", "Bob"], "answer": "Nizar" }
]
ReactDOM.render(
<Question questionChoices={questionChoices} />,
document.getElementById('container')
);
Here's a working copy

Categories

Resources