--- 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>