--- templateEngineOverride: false ---
Dr. Greg Bernstein
Updated April 20th, 2021
this<input> need a closing </input>class attributes must be changed to className similarly for other attribute namesFrom index.jsx in branch BasicForm1
class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<main>
<header>
<h1>Membership</h1>
</header>
<h2>Apply Now!</h2>
<section id="Application">
<label>Name:</label>{" "}
<input
required
minLength="1"
maxLength="30"
id="Name"
type="text"
></input>
<label>email:</label>
<input required maxLength="50" id="Email" type="email"></input>
<label>Password:</label>
<input
required
type="password"
minLength="8"
maxLength="20"
></input>
<label>Confirm Password:</label>
<input
required
type="password"
minLength="8"
maxLength="20"
></input>
<label>Level:</label>
<select required>
<option>Never Done It</option>
<option>Beginner</option>
<option>Intermediate</option>
<option>Foils to TI and Back</option>
<option>Racer</option>
</select>
<label>Comments:</label>
<textarea name="comments" rows="8" cols="20" id="Comments"></textarea>
<button id="Apply">Sign me up!</button>
</section>
<section id="ThanksDialog">
<div className="message">
<h3>Thanks for Signing Up</h3>
<p id="UserInfo"></p>
<button id="Close">Close</button>
</div>
</section>
</main>
);
}
}
value attribute to each widget and “link it” to appropriate stateonInput, onChange or other handler as appropriate (I used arrow functions since these were so small)From index.jsx in branch BasicForm2
class App extends React.Component {
constructor(props) {
super(props);
this.state = {name: "", email: "", password: "", confPassword: "", level: "Beginner", comments: ""};
}
render() {
return (
<main>
<header>
<h1>Membership</h1>
</header>
<h2>Apply Now!</h2>
<section id="Application">
<label>Name:</label>{" "}
<input
required
minLength="1"
maxLength="30"
id="Name"
type="text"
value={this.state.name}
onInput={(event) => this.setState({ name: event.target.value })}
></input>
<label>email:</label>
<input
required
maxLength="50"
id="Email"
type="email"
value={this.state.email}
onInput={(event) => this.setState({ email: event.target.value })}
></input>
<label>Password:</label>
<input
required
type="password"
minLength="8"
maxLength="20"
value={this.state.password}
onInput={(event) => this.setState({ password: event.target.value })}
></input>
<label>Confirm Password:</label>
<input
required
type="password"
minLength="8"
maxLength="20"
value={this.state.confPassword}
onInput={(event) =>
this.setState({ confPassword: event.target.value })
}
></input>
<label>Level:</label>
<select
required
value={this.state.level}
onInput={(event) => this.setState({ level: event.target.value })}
>
<option>Never Done It</option>
<option>Beginner</option>
<option>Intermediate</option>
<option>Foils to TI and Back</option>
<option>Racer</option>
</select>
<label>Comments:</label>
<textarea
name="comments"
rows="8"
cols="20"
value={this.state.comments}
onInput={(event) => this.setState({ comments: event.target.value })}
></textarea>
<button id="Apply">Sign me up!</button>
</section>
<section id="ThanksDialog">
<div className="message">
<h3>Thanks for Signing Up</h3>
<p id="UserInfo"></p>
<button id="Close">Close</button>
</div>
</section>
</main>
);
}
}
<section>From index.jsx in branch BasicForm3
class App extends React.Component {
constructor(props) {
super(props);
this.state = {name: "", email: "", password: "", confPassword: "", level: "Beginner",
comments: "",
dialogClass: ""};
}
submitApplication() {
// In a real application we'd actually send data to a server here
// But all we'll do here is show the welcome/thanks dialog
this.setState({ dialogClass: "show" })
}
render() {
// Check if password and confirmation passwords match here
let message = null;
if (this.state.password.length < 8 || this.state.password !== this.state.confPassword) {
message = <p>Password too short or not confirmed.</p>
} else { // Everything is good create a welcome message
message = <p>Welcome <em>{this.state.name}</em>,{" "}
your email is <em>{this.state.email}</em>,{" "}
your level is <em>{this.state.level}</em>{" "}
and you had the following comments: <em>{this.state.comments}</em></p>
}
return (
<main>
<header>
<h1>Membership</h1>
</header>
<h2>Apply Now!</h2>
<section id="Application">
<label>Name:</label>{" "}
<input
required
minLength="1"
maxLength="30"
id="Name"
type="text"
value={this.state.name}
onInput={(event) => this.setState({ name: event.target.value })}
></input>
<label>email:</label>
<input
required
maxLength="50"
id="Email"
type="email"
value={this.state.email}
onInput={(event) => this.setState({ email: event.target.value })}
></input>
<label>Password:</label>
<input
required
type="password"
minLength="8"
maxLength="20"
value={this.state.password}
onInput={(event) => this.setState({ password: event.target.value })}
></input>
<label>Confirm Password:</label>
<input
required
type="password"
minLength="8"
maxLength="20"
value={this.state.confPassword}
onInput={(event) =>
this.setState({ confPassword: event.target.value })
}
></input>
<label>Level:</label>
<select
required
value={this.state.level}
onInput={(event) => this.setState({ level: event.target.value })}
>
<option>Never Done It</option>
<option>Beginner</option>
<option>Intermediate</option>
<option>Foils to TI and Back</option>
<option>Racer</option>
</select>
<label>Comments:</label>
<textarea
name="comments"
rows="8"
cols="20"
value={this.state.comments}
onInput={(event) => this.setState({ comments: event.target.value })}
></textarea>
<button onClick={this.submitApplication.bind(this)}>
Sign me up!
</button>
</section>
<section id="ThanksDialog" className={this.state.dialogClass}>
<div className="message">
<h3>Thanks for Signing Up</h3>
{message}
<button onClick={(event) => this.setState({ dialogClass: "" })}>
Close
</button>
</div>
</section>
</main>
);
}
}
Will create a new component to add/edit questions to Quiz-O-Matic
Need to take in text for the question, each of the choices and indicate the correct choice.
Example branch classComp4
Form component quickly become complicated!
<textarea> for question<input> for each choice<select> for choosing the answerKeep all the form info in the component state:
class AddQuestion extends React.Component {
constructor(props) {
super(props); // Must call
this.state = {
question: "When can you store plaintext passwords on the server?",
choices: ["If I feel like it", "sometimes", "never"], answer: 2};
}
render() {...}
}
No “functionality” yet since no event handling:
render() { //familiar pattern below!
let choiceItems = this.state.choices.map(function(choice, i) {
return <li key={"choice"+i}>
<button>Delete</button><span> </span>
<input value={choice}/>
</li>
})
const letters = "abcdefghi";
let options = this.state.choices.map(function(choice, i){
return <option key={"opt"+i} value={i}>{letters[i]+". "}</option>;
}) //familiar pattern here too!
return <div className="addQComp">
<p>Question</p>
<textarea value={this.state.question}></textarea>
<p>Choices</p>
<ol style={{listStyleType: "lower-alpha", paddingLeft: "1em"}}>
{choiceItems}</ol>
<p><span>Answer:</span>
<select value={this.state.answer}>{options}</select></p>
<p><button>Add Choice</button><button>Add Question</button></p>
</div>;
}
Branch classComp5
textAreachange(event){
this.setState({question: event.currentTarget.value});
}
onChange attribute to the text area:<textarea value={this.state.question} onChange={this.textAreachange.bind(this)}/>
this, Arrays, and bind()Issues:
Event Handler for choice <input> change:
choiceChange(i, event) {
// Creates a new modified array of choices
let newChoices = this.state.choices.map(function(choice, index){
if (index === i) { // Only changes a particular choice
return event.currentTarget.value; // from the <input> element
} else
return choice;
})
this.setState({choices: newChoices}); // update state
}
The following from render doesn’t work for multiple reasons!
let choiceItems = this.state.choices.map(function(choice, i) {
return <li key={"choice"+i}>
<input onChange={this.choiceChange(i).bind(this)} value={choice}/>
</li>
});
In a different function context!
Put this in a variable with a different name for safekeeping. Also see proper use of bind with additional variable i.
let that = this; // Saves this into another variable for use below
let choiceItems = this.state.choices.map(function(choice, i) {
return <li key={"choice"+i}>
<button onClick={that.delChoice.bind(that, i)}>Delete</button><span> </span>
<input onChange={that.choiceChange.bind(that, i)} value={choice}/>
</li>
});
JSX for add choice button, and add question button:
<p><button onClick={this.addChoice.bind(this)}>Add Choice</button>
<button onClick={this.addQuestion.bind(this)}>Add Question</button></p>
Event handling code, note use of array method:
addChoice() {
// concat method create a new array with added element
this.setState({choices: this.state.choices.concat("")});
}
JSX for delete choice buttons, note bind with i:
let choiceItems = this.state.choices.map(function(choice, i) {
return <li key={"choice"+i}>
<button onClick={that.delChoice.bind(that, i)}>Delete</button><span> </span>
<input onChange={that.choiceChange.bind(that, i)} value={choice}/>
</li>
});
Event handling code, note use of array filter method:
delChoice(i) {
// filter produces a new array
let upChoices = this.state.choices.filter(function(choice, index){
if(index === i)
return false;
else
return true;
})
this.setState({choices: upChoices});
}
AddQuestion component to be able to add a question to the app.questions array is part of its parents state so it can’t modify itquestions.addQuestion() on Quiz-o-Maticclass QuizOMatic extends React.Component {
constructor(props) {
super(props); // Must call
// a member variable called "state" to hold the state as a JS object
this.state = {
show: "addQ", questions: questions,
user: "CS351", score: "0/0", minutes: 12
};
}
addQuestion(q) { // Take a multiple choice question as input
this.setState({questions: this.state.questions.concat(q)});
// Show the quiz so we can see it
this.setState({show: "quiz"});
}
// other stuff
render(){...}
}
addQuestion() downPart of render():
switch (this.state.show) {
case "intro":
contents = <Intro user={this.state.user} takeQuiz={this.quizHandler.bind(this)}/>;
break;
case "quiz":
contents = <Quiz questions={this.state.questions} gradeIt={this.resultHandler.bind(this)}/>;
break;
case "summary":
contents = <Summary user={this.state.user} score={this.state.score}
minutes={this.state.minutes} again={this.quizHandler.bind(this)}/>;
break;
case "addQ": // Passing addQuestion method down here.
contents = <AddQuestion addQuestion={this.addQuestion.bind(this)}/>;
break;
default:
contents = <h2>Warning something went wrong!!!</h2>;
}
AddQuestion component event handler:
addQuestion() { //Too many functions with the same name?
// Put local state into nicely formatted object
let q = {question: this.state.question, choices: this.state.choices,
answer: this.state.answer};
// Call passed down function with new question object
this.props.addQuestion(q);
}
Portion of AddQuestion render:
<p><button onClick={this.addChoice.bind(this)}>Add Choice</button>
<button onClick={this.addQuestion.bind(this)}>Add Question</button></p>