- What is React?
- The Checkbox-Textbox Example using jQuery
- The Checkbox-Textbox Example in React
- Nested Components
- Wrapping it Up
Table of contents generated with markdown-toc
Javascript was the first programming language I learned, sometime around 1998. I've always enjoyed it, but I feel like I had a very early 2000s take on it -- jQuery and MooTools were the popular frameworks. You wrote a lot of your code from scratch, and you walked 5 miles up and down a hill to revert changes.
So, when I dipped my toes into the new world of Node.js and React and npm, I was blown away with both how much easier things got and, ironically, how much more complicated the ecosystem and tooling became.
It took someone explaining it to me in person, and working through my questions, for me to start understanding what React was. Maybe I'm a bit dense here, but the documentation I've come across didn't make sense until another person explained it.
There might not be any getting around it ultimately, but, I'm going to try to explain React as humanely as possible so you can get a sense of it organically. You should already be familiar with Javascript and HTML. Rather than having you setup a local environment and all that, I'll post live examples on jsfiddle.
Let's get going.
What is React?
React is a UI framework created by Facebook. It works well with a design pattern called flux (also Facebook).
The goal is to have a simple, one-way flow of connecting the UI to data and user input. No more having to markup HTML with onEvent
handlers and separately writing a bunch of jQuery with complex relationships.
By one-way, take the basic structure of
<a><b>text</b></a>
. We can write this relation asa -> b
. Any change toa
forces botha
andb
to re-render, while any change tob
only re-renders it.b
knows nothing abouta
.
I'm not going to start with React, though.
The Checkbox-Textbox Example using jQuery
Let's say we have a checkbox where we can enter an expiration date, but we need to explicitly enable it with a checkbox. If the checkbox is unchecked, the field should be blank and disabled. Like this:
<form id='example-01'>
<input type="checkbox" name="enableExpiration" />
<input type="text" name="expirationDate" disabled />
</form>
I'm going to write this using jQuery:
var textbox = $("#example-01 input[name='expirateDate']");
var checkbox = $("#example-01 input[name='enableExpiration']");
checkbox.on('click', function(event) {
if (event.currentTarget.checked) {
textbox.prop('disabled', false);
} else {
textbox.val('').prop('disabled', 'disabled');
}
});
See jsfiddle
Pretty simple. Now let's add some validation -- if the date isn't correct (let's say it's alphanumeric characters only) when you change focus, show a red error box under the controls. Now our HTML might look like this:
<form id='example-02'>
<input type="checkbox" name="enableExpiration" />
<input type="text" name="expirationDate" disabled />
<div class="errorMessage" style="display: none; background: red; color: white">
</div>
</form>
And our Javascript would look like this:
var textbox = $("#example-02 input[name='expirateDate']");
var checkbox = $("#example-02 input[name='enableExpiration']");
var errorMessage = $("#example-02 .errorMessage");
checkbox.on('click', function(event) {
if (event.currentTarget.checked) {
textbox.prop('disabled', false);
} else {
textbox.val('').prop('disabled', true);
}
});
textbox.on('blur', function(event) {
console.log('blur');
if (!textbox.val().match(/^[\w]+$/)) {
errorMessage.show().html('Invalid date!');
} else {
errorMessage.hide();
}
}).on('focus', function(event) {
errorMessage.hide();
});
See jsfiddle
Conclusion
Our current relationships look like this:
Checkbox -> Textbox -> ErrorMessage
It alls looks ok and manageable so far. But as your needs start to grow, this
can start to get out of hand pretty quickly. There's also a lot of boilerplate code. And what happens when you want to re-use this field in other places?
Let's go a step further and pretend we already have a systematic way to manage these sets of fields and create different instances. Is that something we can easily apply to new fields?
The Checkbox-Textbox Example in React
First, let's imagine our checkbox-textbox-error combo from before as a single field called Optional Expiration Date. We could write some JS like this:
// OptionalExpirationDate.js
import React from 'react';
class OptionalExpirationDate extends React.Component {
render() {
return <div className="optional-expiration-date">hello</div>;
}
}
export default OptionalExpirationDate;
If you've never seen ES 6 or JSX, this will look pretty magical. (It still kinda is for me.)
ES 6 and JSX Quick Primer
I'll do a quick breakdown of the code:
import React from 'react';
This loads the React
object from the library 'react'
. You need it anywhere you use the JSX syntax.
class OptionalExpirationDate extends React.Component {
React.Component
is the backbone of what powers React. Our class can now be rendered.
render() { return <div className="optional-expiration-date">hello</div>; }
All React components need a render()
method. It returns JSX, which is a hybrid HTML-Javascript syntax. We're returning the equivalent of <div class="optional-expiration-date">hello</div>
export default OptionalExpirationDate;
This means when where we're doing import ClassName from 'OptionalExpirationDate.js';
, the OptionalExpirationDate
class is what's returned. There are other ways to import and export.
Now, in another file that initializes the page, we could have:
// boot.html
<div id="example-03"></div>
<script type="javascript">
import OptionalExpirationDate from 'OptionalExpirationDate.js';
let opt = <OptionalExpirationDate />;
ReactDOM.render(opt, document.getElementById('example-03'));
</script>
See jsfiddle (code has been changed slightly to work in jsfiddle)
So, when the page renders you'll see <div className="optional-expiration-date">hello</div>
within #example-03
. Let's expand our component:
// OptionalExpirationDate.js
import React from 'react';
class OptionalExpirationDate extends React.Component {
render() {
return <div className="optional-expiration-date">
<input type="checkbox" name="enableExpiration" />
<input type="text" name="expirationDate" disabled />
<div className="errorMessage" style={{display: 'none', background: 'red', color: 'white'}}></div>
</div>;
}
}
export default OptionalExpirationDate;
See jsfiddle
It's almost the same as our initial HTML except for some key things in JSX:
- since
class
is a reserved word, we have to useclassName
- styles are expected to be key-value pairs as a JS object. Without quoting,
attributes like
font-size
becomefontSize
-
style
value is enclosed with{}
-- this means to use JS code instead of a literal value. This opens up a world of possibilities
Making Our Component Interactive
So far everything should've been straightforward. We've only focused on outputting HTML. Now let's do some wiring. We're going to introduce three new things that plunge us into the React world, and this section is going to be long (sorry):
- state (React's way of tracking changes in the UI)
- destructuring (set local variables to/from an object)
- arrow functions (compact inline functions)
// OptionalExpirationDate.js
import React from 'react';
class OptionalExpirationDate extends React.Component {
state = {inputChecked: false, errorMessage: null};
toggleCheckbox() {
this.setState({...this.state, inputChecked: !this.state.inputChecked});
return this;
}
setErrorMessage(msg) {
this.setState({...this.state, errorMessage: msg});
return this;
}
render() {
return <div className="optional-expiration-date">
<input type="checkbox" name="enableExpiration" onClick={() => this.toggleCheckbox()} />
<input type="text" name="expirationDate"
disabled={this.state.inputChecked ? false : true} />
<div className="errorMessage"
style={{
display: this.state.errorMessage == null ? 'none' : 'block',
background: 'red', color: 'white'
}}>{this.state.errorMessage}</div>
</div>;
}
}
Reading through it, it should still make sense at a high level. And look, all the functionality is sitting close with the HTML! Do you see any selectors being used? Are we even directly referencing an HTML element here? (Nope and nope.)
How is this possible?
See jsfiddle
State (this.state
)
The first thing to explain is state. Notice we have toggleCheckbox()
and setErrorMessage()
both call setState({...})
. When this happens, React knows to reprocess this component (and in effect, all its children). This is essentially the core of the React/flux pattern.
Destructuring (ES 6)
The second thing to explain is destructuring. Let's say you have an object:
let map = {a: 1, b: 2};
If we want to merge this with another object, there are a bunch of ways to do this in old JS. We'll go with a manual approach:
let mapNew = {a: map.a, b: map.b, c: 3};
With destructuring, the following is equivalent:
let mapNew = {...map, c: 3};
The ...map
is essentially copy every key-value from map
when it's on the right side.
Another way destructuring is frequently used is as follows:
let map = {a: 1, b: 2};
let mapNew = {...maps, c: 3, d: 4};
let {a, b, ...rest} = mapNew;
The last line is equivalent to:
let a = mapNew.a, b = mapNew.b, rest = {c: mapNew.c, d: mapNew.d};
The ...rest
means copy all the remaining key-value pairs into rest
when it's on the left side.
The final way to use destructuring is to copy local variables into key-value pairs:
let a = 1, b = 2, c = 3, d = 4;
let map = {a, b};
let mapNew = {...map, c, d};
// equivalent to
let map = {a: a, b: b};
let mapNew = {a: map.a, b: map.b, c: c, d: d};
Try it Out
Copy & paste the following into your browser dev console:
let a = 1, b = 2;
let map = {a, b};
console.log('a, b to map', map);
let map2 = {...map, c: 3, d: 4};
console.log('map2 merged with map + {c:3,d:4}', map2);
let {c, d} = map2;
console.log('c, d extracted from map2', c, d);
Arrow Functions (ES 6)
The third thing to explain is arrow functions. You see it in onClick={() => this.toggleCheckbox()}
.
This is similar to onClick={function() { this.toggleCheckbox(); }}
but not the same. With the function() { }
syntax, this
isn't actually bound to anything. To make that work in this context, you'd actually have to do the following:
// in render()
const me = this;
return <div className="optional-expiration-date">
<input type="checkbox" name="enableExpiration" onClick={function() {me.toggleCheckbox();}} />
...
</div>;
With arrow functions, this
is the object the enclosing method is bound to. That's advantage #1 (and the biggest advantage, honestly).
If you're writing a one-liner, () => statement
executes the statement and returns the value. For multiple statements (or if you want to be more verbose), you'd write () => { return statement; }
. If you had a single argument, you could write (arg) => statement
or arg => statement
. That's advantage #2.
Try it Out
Copy & paste the following into your browser dev console:
const f1 = () => console.log('called f1');
const f2 = (a, b) => console.log('called f2', a, b);
const f3 = (a, b) => {
const c = a + b;
console.log('called f3', a, '+', b, '=', c);
}
f1();
f2(1, 2);
f3(1, 2);
class Throwaway {
constructor() {
this.a = '#instance property a';
}
getFunc() {
return () => this.a;
}
}
let t = new Throwaway();
let callback = t.getFunc();
console.log('retrieving t.a from an arrow function callback', callback());
Conclusion
Our initial HTML/jQuery field has become a single unit now encapsulated by one class. The code is line-for-line longer, but look at what happens:
- we don't have to write/track messy CSS selectors
- we treat HTML as an object, which directly ties the functionality with the context
- everything is acting as a logical unit
So, less codesoup. (At least in this example. The rest of the React world isn't perfect.)
Here's our relationships now:
OptionalExpirationDate
└-> Checkbox
└-> Textbox
└-> ErrorMessage
Lifting State Up
Another thing that happened in our component is that the sibling components don't know anything about each other. It's the task of the parent component to figure out what to pass on to the children. This also reduces complexity. This, at its simplest, is what's called lifting state up. Understanding this will make React code easier to work with.
With this baseline understanding, let's finally add validation to our React example.
Nested Components
We haven't worked with nested component yet. In fact, anything in the form <lowercase ...>
is always treated as final HTML output. So, let's make the error message box its own component. We should be able to pass it a message
that's either a string or null
-- if it's null, it should be hidden.
// in ErrorMessage.js
import React from 'react';
class ErrorMessage extends React.Component {
render() {
const {message} = this.props;
const styles = {
display: message == null ? 'none' : 'block',
background:'red',
color: 'white'
};
return <div className="errorMessage" style={styles}>{message}</div>
}
}
export default ErrorMessage;
What's this.props
???
Properties (this.props
)
We've seen the JSX <element key={value} key2="value" ... />
. When element
is a component, these get converted to the object {key: value, key2: 'value', ...}
which gets set on the props
property of a React.Component
. The default constructor, in fact, is construct(props)
.
In ErrorMessage.render()
, we're extracting the message
property from this.props
.
Unlike this.state
, we never directly change this.props
. Think of it as the baseline rules that define how a component should behave, along with any rules/values/etc. that should be passed along to children (more on that later).
Using Our New Component
We're simply going to import ErrorMessage
and swap it in for our old <div>
:
// OptionalExpirationDate.js
import React from 'react';
import ErrorMessage from './ErrorMessage';
class OptionalExpirationDate extends React.Component {
state = {inputChecked: false, errorMessage: null};
toggleCheckbox() {
this.setState({...this.state, inputChecked: !this.state.inputChecked});
return this;
}
setErrorMessage(msg) {
this.setState({...this.state, errorMessage: msg});
return this;
}
render() {
return <div className="optional-expiration-date">
<input type="checkbox" name="enableExpiration" onClick={() => this.toggleCheckbox()} />
<input type="text" name="expirationDate"
disabled={this.state.inputChecked ? false : true} />
<ErrorMessage message={this.state.errorMessage} />
</div>;
}
}
Pretty nifty, right? Let's do our final bit: adding validation.
Communicate with Parent Component (indirectly)
For this, we're going to make another component for the textbox, and we'll expect two properties:
-
validationCallback
should be a callback function to invoke when there's an error. It should take a single parametermessage
-
disabled
should be a boolean to enable or disable the field
// Textbox.js
import React from 'react';
class Textbox extends React.Component {
validate(event) {
const callback = this.props.validationCallback || (() => {});
if (!event.currentTarget.value.match(/^\w+$/)) {
callback('Invalid date: ' + event.currentTarget.value);
}
}
render() {
return <input type="text" name="expirationDate" disabled={this.props.disabled ? true : false}
onBlur={(event) => this.validate(event)} />
}
}
Now, our primary component will look like this:
// OptionalExpirationDate.js
import React from 'react';
import ErrorMessage from './ErrorMessage';
import Textbox from './Textbox';
class OptionalExpirationDate extends React.Component {
state = {inputChecked: false, errorMessage: null};
toggleCheckbox() {
this.setState({...this.state, inputChecked: !this.state.inputChecked});
return this;
}
setErrorMessage(msg) {
this.setState({...this.state, errorMessage: msg});
return this;
}
render() {
return <div className="optional-expiration-date">
<input type="checkbox" name="enableExpiration" onClick={() => this.toggleCheckbox()} />
<Textbox validationCallback={(message) => this.setErrorMessage(message)} disabled={!this.state.inputChecked} />
<ErrorMessage message={this.state.errorMessage} />
</div>;
}
}
See jsfiddle
So what's happening here? We're passing an inline function (message) => this.setErrorMessage(message)
that's bound to the current OptionalExpirationDate
component. When ExpirationDate
sees a non-null value for its validationCallback
property, it uses it to communicate a validation error.
However, it doesn't know what it's communicating with! All it cares about is that there's a callback -- what happens next is not its concern. This is what I mean by indirectly communicating with the parent. And, it still doesn't know the state of its siblings.
Conclusion
Now we've got a React example that's similar to our jQuery example, but we're still not dealing with any selectors, and every individual field still doesn't need to worry about any of its siblings.
Taking a step back, we can now drop this field in wherever we want and it'll automatically work!
const form = <form>
<OptionalExpirationDate />
<OptionalExpirationDate />
</form>
Too simple. Of course, we would now need to be able to control the field names so that one date doesn't overwrite the other. What do you think needs to be done?
Solution Spoiler
- pass in
expirationName
toOptionalExpirationDate
:<OptionalExpirationDate expirationName="newField" />
- pass down name to
ExpirationDate
:<ExpirationDate name={this.props.expirationName} ... />
- in date component, use passed-in name or a default:
<input name={this.props.name || 'expirationDate'} ... />
See jsfiddle
Wrapping it Up
Hopefully this has demonstrated, in an easy-to-understand way, some of the core concepts of what React is/does:
- Create one direction of data flow so that each component only needs to know about itself and setup its children
- Build components to be reusable and customizable with little effort
How does React do this magic?
I've punted on how this all works until now because I didn't want you to get lost in details before you even got to what React is. I'm still not going to do this process justice, but here's a brief overview:
First is JSX. This is a language extension to Javascript. It's not a standard (yet). So how does it work?
React apps are compiled using npm or yarn (or some other build tool). Part of the compilation process involves a library called babel, which scans the files being imported and carries out transformations where it needs to. As mentioned before, whenever we have JSX we always need to have import React from 'react';
in our file. This signals to babel to treat this file as having JSX. So <SomeComponent prop1="whatever" ... />
gets translated to:
let someComponent = React.createElement(
'tagName',
{prop1: "whatever", ...},
[...children...]
);
Now, if you start poking around at other React libs, you'll quickly find that they are written in all sorts of JS variants. But what ties it all together is transpiling -- converting code from one language (or variant) to another. This is what also makes cutting-edge JS syntax available on browsers.
Next is React itself. How does it connect to the actual DOM? React maintains a virtual DOM. Each component you create/inject becomes part of this virtual DOM. When you call setState()
, it only affects the component that's called. This in turn updates the virtual DOM, which then renders new HTML in the browser DOM. Remember ReactDOM.render()
above? This is what connects the virtual DOM to the browser DOM.
Further Readings
If you want a more in-depth intro, checkout A Complete Beginner's Guide to React.
To setup react in npm, see this tutorial -- note that if you haven't worked with node before, this could be daunting. So, maybe also check out this tutorial on npm.
Of course, jsfiddle is the quickest way to go if you just want to play around. Fork one of the examples above to get running quickly.
Next Up
We're only scratching the surface here. In the next installment, we'll go a bit deeper with props
, state
, child components, and higher order components (HOC). And in another installment, we'll actually talk about data flowing into and out of components.
Hope you enjoyed! Please let me know if there are areas I can improve, or if I've totally miscommunicated a concept! Smash that like button and subscribe.
Top comments (0)