Dr. Greg Bernstein
Updated September 17th, 2021
this
, and JavaScript bind()
Class components let us define components with internal state
Class components let our components participate/extend the components life cycle.
Three planned stages of application use:
Components files:
intro.js
questions.js
summary.js
Use a JavaScript object derived from React.Component to keep track of state.
class MyComponent extends React.Component {
constructor(props) {
super(props); // Must call
// Set up state here
}
// Renders component based on current state and props
render() {
// Any code you like
return // Must return a JSX element here
}
}
examples branch classComp1
class 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: "intro", questions: questions,
user: "CS351", score: "0/0", minutes: 12};
}
// Renders component based on current state and props
render() {
let contents = null;
switch (this.state.show) {
case "intro":
contents = <Intro user={this.state.user}/>;
break;
case "quiz":
contents = <Quiz questions={this.state.questions}/>;
break;
case "summary":
contents = <Summary user={this.state.user} score={this.state.score}
minutes={this.state.minutes}/>;
break;
default:
contents = <h2>Warning something went wrong!!!</h2>;
}
return <div><h1>Quiz-O-Matic</h1>
{contents}
</div>
}
}
Both of these React developer tool add-ons allow you to inspect components state and properties and modify them!
Firefox React Dev Tool. I use this!
Chrome React Dev Tools. I use this too!
intro
Initial state show: "intro"
summary
Use dev tool to change state show: "quiz"
Important!
When a class components state is properly changed React will call its render() method to update the component
“Do Not Modify State Directly”, always use the setState()
method.
“State Updates are Merged”. Okay to call setState()
from different places with different pieces of the state.
Let’s change state via a button bar
Just adds some buttons! Branch: classComp2
render() {
let contents = null;
let buttonBar = <div className="buttonBar">
<button>Intro</button>
<button>Quiz Me</button>
<button>Results</button>
</div>;
switch (this.state.show) {
case "intro":
contents = <Intro user={this.state.user}/>;
break;
case "quiz":
contents = <Quiz questions={this.state.questions}/>;
break;
case "summary":
contents = <Summary user={this.state.user} score={this.state.score}
minutes={this.state.minutes}/>;
break;
default:
contents = <h2>Warning something went wrong!!!</h2>;
}
return <div>
{buttonBar}
<main>
<h1>Quiz-O-Matic</h1>
{contents}
</main>
</div>
}
From MDN on-event handlers: “The Web platform provides several ways to get notified of DOM events.”
<button onclick="return handleClick(event);">
document.getElementById("mybutton").onclick = function(event) { ... }
addEventListener()
interface is the most general and flexible approachFrom React: Handling Events:
<button onClick={activateLasers}>
Activate Lasers
</button>
Defining event handlers in the class component:
class QuizOMatic extends React.Component {
constructor(props) {
super(props); // Must call
this.state = {
show: "intro", questions: questions,
user: "CS351", score: "0/0", minutes: 12
};
}
introHandler(event) {
console.log(event);
console.log(event.target)
console.log("You pressed a button");
this.setState({show: "intro"});
}
quizHandler(event) {
this.setState({show: "quiz"});
}
resultHandler(event) {
this.setState({show: "summary"});
}
render() {...}
}
setState(...)
The setState(...)
member function is defined by React in the React.Component
class.
It’s purpose is to let React know that the state has been changed so it can update the DOM.
Not using setState(...)
to change the state will break React. The only place we do not use setState(...)
is when we define the initial state in the constructor.
Need to be careful with this
:
class QuizOMatic extends React.Component {
// constructor and event handlers not shown
render() {
let contents = null;
// Notice the syntax below
let buttonBar = <div className="buttonBar">
<button onClick={this.introHandler.bind(this)}>Intro</button>
<button onClick={this.quizHandler.bind(this)}>Quiz Me</button>
<button onClick={this.resultHandler.bind(this)}>Results</button>
</div>;
switch (this.state.show) {
case "intro":
contents = <Intro user={this.state.user}/>;
break;
case "quiz":
contents = <Quiz questions={this.state.questions}/>;
break;
case "summary":
contents = <Summary user={this.state.user} score={this.state.score}
minutes={this.state.minutes}/>;
break;
default:
contents = <h2>Warning something went wrong!!!</h2>;
}
return <div>
{buttonBar}
<main>
<h1>Quiz-O-Matic</h1>
{contents}
</main>
</div>
}
}
Try it without the bind
call:
class QuizOMatic extends React.Component {
// constructor and event handlers not shown
render() {
let contents = null;
// Notice the syntax below
let buttonBar = <div className="buttonBar">
<button onClick={this.introHandler}>Intro</button>
<button onClick={this.quizHandler}>Quiz Me</button>
<button onClick={this.resultHandler}>Results</button>
</div>;
switch (this.state.show) {
case "intro":
contents = <Intro user={this.state.user}/>;
break;
case "quiz":
contents = <Quiz questions={this.state.questions}/>;
break;
case "summary":
contents = <Summary user={this.state.user} score={this.state.score}
minutes={this.state.minutes}/>;
break;
default:
contents = <h2>Warning something went wrong!!!</h2>;
}
return <div>
{buttonBar}
<main>
<h1>Quiz-O-Matic</h1>
{contents}
</main>
</div>
}
}
this
ProblemThe mysterious undefined this error:
From MDN bind()
The bind() method creates a new function that, when called, has its
this
keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
function.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg
is the this
you want to bind and arg1
, arg2
, etc are any other arguments you want to bind.bind()
Example 1From MDN bind()
var module = {x: 42, getX: function() {return this.x;}}
var unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// expected output: undefined
var boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// expected output: 42
bind()
Formal DefinitionThe bind() function creates a new bound function (BF). A BF is an exotic function object (a term from ECMAScript 2015) that wraps the original function object. Calling a BF generally results in the execution of its wrapped function.
This is a functional programming idea. See Wikipedia Currying and JavaScript currying and partials
Python also has support functools — Higher-order functions and operations on callable objects
bind()
Example without this
Just use null
where the thisArg
would go:
function addem(x, y) {return x + y};
var add3 = addem.bind(null, 3);
console.log(addem(2, 7));
console.log(add3(7));
With Bind
users = ["Dr. B", "Student X", "Student Y"];
function sayHi(name) {
alert("Hi " + name);
}
// Using array destructuring assignment below
[hiDrB, hiX, hiY] = users.map(x => sayHi.bind(null, x));
// Try calling them
hiDrB();
hiX();
hiY();
Without Bind (closure)
users = ["Dr. B", "Student X", "Student Y"];
function sayHi(name) {
alert("Hi " + name);
}
// A function that returns a function (closure!)
function hiFunc(s) {
return function() {
sayHi(s);
}
}
// Using array destructuring assignment below
[hiDrB, hiX, hiY] = users.map(x => hiFunc(x));
// Try calling them
hiDrB();
hiX();
hiY();
bind
to produce “new” functions with fewer arguments from a function with more arguments by “fixing” some of those arguments with bind
.bind
is the primary way for us to set this for a function in JavaScriptGive React a “new thingy” rather than changing a “sub part” of that “thingy” and giving it back to React.
Easy for a property that is a single value:
// Properly updating state of the *show* property
this.setState({show: "quiz"});
this.state = {
show: "intro", questions: questions,
user: "CS351", score: "0/0", minutes: 12
};
Suppose we allow users to add questions to the quiz.
Straightforward and wrong approach:
// Wrong modifying questions without setState
this.state.questions.push({question:"What does CSS mean", choices: []});
// Wrong: looks like the same object as before React won't see change
this.setState({questions: this.state.questions})
We need to give React a new array but we want to be efficient
Use Array.concat as it returns a new array instance:
// Creates a new array with the additional question
let newQs = this.state.questions.concat({question:"What does CSS mean", choices: []})
// newQs is a different array from state.questions so React will update
this.setState({questions: newQs});
Use JS Array methods that return new arrays:
Removing elements from a state array: Use the Array.filter method.
Updating elements in a state array: Use the Array.map method.
Remember that both filter
and map
callbacks receive current value, index, and the entire array as parameters.
Suppose you need to change part of a state property that is a JS Object
this.state = {
show: "intro", questions: questions,
user: {first: "CS", last: "351", netid: "tb1524", inClass: true},
score: "0/0", minutes: 12
};
Object.assign({},...)
to create a copy of the objectsetState()
let newUser = Object.assign({}, this.state.user);
// Modify newUser any way you like
this.setState({user: newUser}); //React will see changes
From The Data Flows Down
Neither parent nor child components can know if a certain component is stateful or stateless, and they shouldn’t care whether it is defined as a function or a class.
This is why state is often called local or encapsulated. It is not accessible to any component other than the one that owns and sets it.
From The Data Flows Down
A component may choose to pass its state down as props to its child components
This is commonly called a “unidirectional” data flow. Any state is always owned by some specific component, and any data or UI derived from that state can only affect components “below” them in the tree.
You cannot write to props
they should be treated as read-only!
“Data flows down” is the theme, what can we do?
Why would you want to do that?
See branch classComp3
See branch classComp3
See branch classComp3
We already have event handler functions defined to switch states
Pass the appropriate functions down to child components via properties
So “data flows down” but functions can call up!
Updated QuizOMatic component render
:
render() {
let contents = null;
let buttonBar = <div className="buttonBar">
<button onClick={this.introHandler.bind(this)}>About</button>
</div>;
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;
default:
contents = <h2>Warning something went wrong!!!</h2>;
}
return <div>
{buttonBar}
<main>
<h1>Quiz-O-Matic</h1>
{contents}
</main>
</div>
}
Updated Quiz component
function Quiz(props) {
let allQs = props.questions.map(function(mChoice, i){
return <Question key={"q" + i} multiChoiceQ={mChoice} />
})
return <div>
<h2>Quiz</h2>
<button onClick={props.gradeIt}>Grade it!</button>
{allQs}
</div>;
}
Updated Summary component
function Summary(props) {
let user = "Friend";
if (props.user) {
user = props.user;
}
return <div>
<h2>Summary</h2>
<p>{user} scored {props.score}, in {props.minutes} minutes.</p>
<p>Keep up the good work and come back soon!</p>
<button onClick={props.again}>Take Another Quiz</button>
</div>;
}