Let’s say you already know how to write vanilla Javascript or jQuery to do some app-like things. You can fetch data, send data, and manipulate the DOM without any frameworks. Why would you want to learn React if you can already do the things you want to do without it?
What is React?
React is a Javascript library that makes it easier to build user interfaces for single-page apps. You build components that end up working like your own custom HTML tags right in your Javascript.
You write something that looks like HTML directly in your React code. It’s called JSX. You can include Javascript expressions inside your JSX so that you can do cool stuff like output data in the rendered component as it changes.
You can stuff a whole bunch of actual HTML elements into a single component and, every time you use that component, the browser renders all the elements contained within it. Your component can also bundle behaviors and even styling that will come along with it anytime you reuse it.
Your component can have state. This is a collection of variables that store data important to that component. If your component’s state changes, you can update the rendered component to reflect the change in state.
Each component has a lifecycle. You can write your own code inside pre-defined lifecycle methods on your component. When the event described by that method happens, React runs your lifecycle method. Here are a few examples of lifecycle methods and what triggers them:
-
constructor()
– Runs as soon as the component is created. -
componentDidMount()
– Runs once the component is added to the DOM. This is a great place to get data out of a database or an external API to use in your component and add it to the component’s state. -
componentDidUpdate()
– Runs when a component is updated by React. This one will run when the component’s state change or when one of the component’s properties gets a new value.
That’s React in a nutshell, although it’s a massive subject. This article isn’t intended as a tutorial; I’m just trying to give you enough context so I can show you why it is useful. If you want to learn React, you might start with the official tutorial.
Updating data in the DOM is a Pain in the 🍑
The reason updating data is a pain is not really that it’s difficult with vanilla Javascript; it’s more that it’s hard to reason about after the fact. The actual process is pretty simple. I just select an element and change its innerHTML
property. Voila! Data updated.
const elementToChange = document.querySelector('h1');
elementToChange.innerHTML = "New Heading Value";
Now, when we have to go back later to maintain this app, we need to look at all the different places we’ve updated the DOM. If the data comes out wrong on the page, it could be a problem at any of those points. Feeding data directly into the DOM via innerHTML
is also dangerous because an attacker could potentially run their own code on your site, stealing information from your users.
In React, we maintain the data that is important to the application in its state and we let React worry about updating the DOM as that data changes.
Widely Supported Components
Think about a sign-in form. In our heads, it’s a single thing, but, on the web, it’s usually five different elements: two labels, two input fields, and a button.
Wouldn’t it be nice if you could think of that as a single component even as you’re building your app? Good news, everyone! Web Components lets you do just that without any frameworks… as long as you only need to support later versions of Chrome, Firefox, and Opera.
That’s great, but chances are you’ll need wider browser support. React allows you to build components that work with IE 9+ and all the modern browsers so that, the next time you want to add a sign-in form to a view, you can just add a sign-in form like this:
<SignInForm />
You’ll have to build that component once yourself (since React doesn’t innately know what a “SignInForm” should be in your app), but the point is that you have the option to build it only once and reuse it as often as you like. The way it looks and its behaviors will travel with it wherever it goes.
Need help becoming a web developer? I can help no matter where you are in your transition. Request a free mentoring session at RadDevon!
An Example: Sunrise Times
To show the difference in the two, I’ve built a simple app that shows sunrise and sunset times given your latitude and longitude.
Sunrise Times (Vanilla JS)
Here’s the Javascript for the vanilla version of the app. If you want to see the HTML and CSS, check out those tabs on the Codepen demo embedded below.
function debounced(delay, fn) {
let timerId;
return function(...args) {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
fn(...args);
timerId = null;
}, delay);
};
}
function updateTimes(lat, long) {
if (lat && long) {
return fetch(`https://api.sunrise-sunset.org/json?lat=${lat}&lng=${long}`)
.then(response => {
if (!response.ok) {
sunriseTimeElement.innerHTML = "Invalid";
sunsetTimeElement.innerHTML = "Invalid";
throw Error(`${response.status} response on times request`);
}
return response.json();
})
.then(data => {
sunriseTimeElement.innerHTML = data.results.sunrise;
sunsetTimeElement.innerHTML = data.results.sunset;
})
.catch(error => {
console.error(error.message);
});
}
}
function updateTimesFromInput() {
const lat = latField.value;
const long = longField.value;
updateTimes(lat, long);
}
const updateTimesFromInputDebounced = debounced(500, updateTimesFromInput);
const sunriseTimeElement = document.querySelector(".sunrise .time");
const sunsetTimeElement = document.querySelector(".sunset .time");
const latField = document.querySelector("#lat");
const longField = document.querySelector("#long");
navigator.geolocation.getCurrentPosition(function(position) {
const lat = position.coords.latitude;
const long = position.coords.longitude;
latField.value = lat;
longField.value = long;
updateTimes(lat, long);
});
[latField, longField].forEach(field => {
const events = ["keyup", "change", "input"];
events.forEach(event => {
field.addEventListener(event, updateTimesFromInputDebounced);
});
});
See the Pen Sunrise app (vanilla variation) by Devon Campbell (@raddevon) on CodePen.
You’ll notice a few nice things about this version. It’s much shorter than the React version. That’s partly because it doesn’t use components (which add some overhead in the React version) and partly because the state is all in the document (which is what makes it hard to maintain and reason about when something goes wrong).
Some other notes that might help you gain context:
- I’ve used number fields so I don’t have to validate. Actually, I should still be doing validation since not all browsers support number fields, but I’m not worried about making this example production-ready.
- The entire document (heading, form, and output) are already represented in the HTML document. All we do in Javascript after-the-fact is update the values of the outputs after getting the results from the new inputs.
- Since my code depends on the right elements being selected, the app could break if someone comes behind me and changes the classes or IDs of the elements. That’s how I’m selecting them. Since the state is maintained there, if I can’t get to them, the app can’t do what it needs to do.
- Note the tedious way I have to bind multiple events to make sure I capture any changes to the lat and long fields. Number fields emit different events depending on how they are changed, so I have to create a binding for each one of those. (You’ll see what I mean at the end of the Javascript.)
- You’ll notice a weird
debounced
function in both this and the React version of this app. The times come from an API. I don’t want to wear out my welcome with that API, so I don’t really want to make a request for every single keypress any of my users makes. The debounced function limits the frequency of calls to a function. At most, this app will make a request to the API once every half-second.
Sunrise Times (React)
Here’s the Javascript for the React version of the app. Again, you can see the HTML and CSS in the Codepen demo embedded below if you’re curious.
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function debounced(delay, fn) {
let timerId;
return function(...args) {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
fn(...args);
timerId = null;
}, delay);
};
}
class CoordinatesForm extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<form>
<LatField
updateLat={newLat => {
this.props.updateCoords({ lat: newLat });
}}
lat={this.props.lat}
/>
<LongField
updateLong={newLong => {
this.props.updateCoords({ long: newLong });
}}
long={this.props.long}
/>
</form>
);
}
}
class LatField extends React.Component {
constructor(props) {
super(props);
this.state = {
lat: this.props.lat
};
}
componentDidUpdate(prevProps) {
const lat = this.props.lat;
if (lat !== prevProps.lat) {
this.setState({ lat });
}
}
updateLat = event => {
let newLat = event.target.value;
if (!newLat) {
this.setState({ lat: 0 });
return this.props.updateLat(0);
}
if (isNumeric(newLat)) {
newLat = parseFloat(newLat);
} else if (newLat !== "-") {
return;
}
this.setState({ lat: newLat });
this.props.updateLat(newLat);
};
render() {
return (
<label for="">
lat:
<input
type="text"
id="lat"
value={this.state.lat}
onChange={this.updateLat}
/>
</label>
);
}
}
class LongField extends React.Component {
constructor(props) {
super(props);
this.state = {
long: this.props.long
};
}
componentDidUpdate(prevProps) {
const long = this.props.long;
if (long !== prevProps.long) {
this.setState({ long });
}
}
updateLong = event => {
let newLong = event.target.value;
if (!newLong) {
this.setState({ long: 0 });
return this.props.updateLong(0);
}
if (isNumeric(newLong)) {
newLong = parseFloat(newLong);
} else if (newLong !== "-") {
return;
}
this.setState({ long: newLong });
this.props.updateLong(newLong);
};
render() {
return (
<label for="">
long:
<input
type="text"
id="long"
value={this.state.long}
onChange={this.updateLong}
/>
</label>
);
}
}
class TimesDisplay extends React.Component {
constructor(props) {
super(props);
this.state = {
sunrise: "Unknown",
sunset: "Unknown"
};
}
componentDidUpdate(prevProps) {
const lat = this.props.lat;
const long = this.props.long;
if (lat !== prevProps.lat || long !== prevProps.long) {
this.updateTimesDebounced(lat, long);
}
}
updateTimes = (lat, long) => {
if (isNumeric(lat) && isNumeric(long)) {
return fetch(`https://api.sunrise-sunset.org/json?lat=${lat}&lng=${long}`)
.then(response => {
if (!response.ok) {
this.setState({
sunrise: "Invalid",
sunset: "Invalid"
});
throw Error(`${response.status} response on times request`);
}
return response.json();
})
.then(data => {
this.setState({
sunrise: data.results.sunrise,
sunset: data.results.sunset
});
})
.catch(error => {
console.error(error.message);
});
}
}
updateTimesDebounced = debounced(500, this.updateTimes)
render() {
return (
<div>
<SunriseTime time={this.state.sunrise} />
<SunsetTime time={this.state.sunset} />
</div>
);
}
}
class SunriseTime extends React.Component {
render() {
return <div class="sunrise">Sunrise: {this.props.time}</div>;
}
}
class SunsetTime extends React.Component {
render() {
return <div class="sunset">Sunset: {this.props.time}</div>;
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
lat: 0,
long: 0
};
}
updateCoords = updateObject => {
this.setState(updateObject);
};
componentDidMount() {
navigator.geolocation.getCurrentPosition(position => {
const lat = position.coords.latitude;
const long = position.coords.longitude;
this.setState({ lat, long });
}, error => {
console.error('Couldn\'t get your current position from the browser');
});
}
render() {
return (
<div>
<CoordinatesForm
lat={this.state.lat}
long={this.state.long}
updateCoords={this.updateCoords}
/>
<TimesDisplay lat={this.state.lat} long={this.state.long} />
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector(".app"));
See the Pen Sunrise app (React variation) by Devon Campbell (@raddevon) on CodePen.
I’m a fan of React, but I’ll readily admit I was shocked how much more code this took to accomplish in React. Most of that is due to the fact that I have componentized everything and I have to share state between the components. Here’s what I mean.
There’s an outer component I call “App” that contains everything else and holds the state that is used by all the other components. Inside the app component are two components: “CoordinatesForm” and “TimesDisplay.” Those have two components each, one corresponding to each of their two values (lat/long and sunrise/sunset respectively). The user will make a change in, for example, the “lat” field. That change needs to be reflected in the state of the App since components can’t share data easily except through a common ancestor.
That means, I’m passing state up and down through the component tree. In React, to set a parent’s state, I need to create a method on the parent component that sets its state. Then, I can pass that down to children through props (React’s name for data passed into child components). Children call that method and pass in the data they need to get into the ancestor’s state.
For data to go down, I just keep passing it through props until it gets to the component that needs it. It isn’t especially hard to do, but it does take a lot more code that just manipulating the DOM directly.
The upside of all this is that, even though I have to get a bit fiddly with the state, I only ever need to change the state. I never have to write any brittle DOM manipulation. Since I’m using the properties that get passed down in the rendered components, React intelligently updates the components when those properties change.
Some more context:
- The HTML document in this version is very sparse. That’s because most of the page gets rendered by React as the components are rendered. The HTML needed only an element for me to render the React app to.
- React didn’t seem to want to cooperate with the number inputs I used in the other app, so I’ve decided to use standard text inputs here with some validation. That validation added some code too. There’s probably a way to make the number input work better, but the app works fine as it is.
The Upshot: Why You Should Care
You can see how, in a large app with lots of data and UI changes happening, React gives you a nice way to separate out components and worry only about the data. It’s not the right fit for every single project, but it’s great to build some structure around your project and make it easier to maintain.
If you’re a developer building web applications with more than a couple of moving parts, you should pay attention to React. It can save you and others maintaining your code a lot of heartache in the future.
Top comments (8)
If you don't mind, I have a couple of thoughts about the article. It looks very well, but it might create some confusing impressions:
It just not true. Yes - native support, for now, is shipped in Chrome, Safari, Firefox, Opera, Internet Browser, UC Browser, and many more, but not in IE or Edge. However, with shims and polyfills provided by the community you can try to use web components today! Especially polyfill for creating custom elements covers almost 100% of the specification.
Someone who has no idea what the React is may have one thought after reading that - JSX is an integral part of the library, and it has to be used. It's not true, even though it is still recommended. It might be a good idea to point it out.
And the sentence before that code snippet... A newbie can understand, that with React is possible to just put a new fancy created element in HTML document. You don't explain, that it has to be used with the render method:
ReactDOM.render()
.I would also recommend simplifying your code examples by hiding not important implementation details. In the code snippet, you can import your methods, which fetches GPS data or update something, and only show the DOM part from vanilla example and React part from the latter. Your first example is cut off in the middle of the function (?). You also compare it to the react example before you show the react example. If they would be shorter you could show them one after another.
And the last thing. Your form example in React can be simpler. Lat and Long fields could use one React component, they are almost identical - the only difference is with what callback they call. You can parametrize that with name prop for example.
By the way, I hope I did not offend you. Again, it is nice to read more articles from the DEV community.
Thanks for your feedback, Dominik. I didn't realize I had not included all the code in the first example, so that was extremely helpful! Some of the other feedback will be incorporated into future posts.
I'm not offended. I appreciate your taking the time to read! 😄
Hmmm...you could replace "React" with "Angular" (or "Vue", or whatever) and say the same thing. So tell me again why should I learn React?
Hello Devon, great article! I have to say that React is also very simple to use when you are a beginner in web development, very clear to understand with a good documentation.
My advice for those who hesitate to start learning it: read this article, go on the web site of React JS and have fun! 🌟 🌟 🌟
My initial answer to the title is "because it's awesome" but seriously though doing things in React may look like a lot of extra work initially but taking that extra work makes for allowing an application to grow and scale without the additional complexity and additional mess of other approaches. Great article!
React is something I have looked at but have chosen not to pursue. React has many fine attributes especially that it offers a way to encapsulate functionality as components as you describe. However, it also comes with an unwelcome feature you mention only by allusion: the build step.
In the past I have used the Sencha framework (when it was free and called ExtJS and before it became hideously expensive) and React has many similarities. Unfortunately, one of the similarities is the need to execute a build process to transform the React source into a package of actual JavaScript.
To me, an attraction of using a scripting language is that it is possible to change code and run it. More importantly, the code seen in the development tools windows of the browser is the code that's been written. This is not the case with React. Instead, its necessary to install the React developer tools to help the developer relate the code executing in the browser to the React source.
The encapsulation offered by React is welcome but for me it does not justify the cost of retooling the development process to change the way writing code, testing, user validation and documentation is accomplished.
If we are going to retool the whole cycle and accept the need for a build process I want there to be bigger gains so I will be waiting until one or more of the existing strongly typed languages such as C++, Java or C# are able to emit reliable web assembly packages. Using a tool like Razor (based on C#) offers the prospect of being able to run code closer to the machine and so gain performance benefits while also getting access to the existing strong typing, build optimisation, profiling tools and OOP capabilities of a mature development environment.
This is not an option for a group that needs to move today or needs to be able to support IE 9 today but it may be a more cost effective option for some groups that can take a slightly longer view.
Create React App addresses some of the problems you've mentioned here. It allows me to write code, save it, and see the results almost immediately in the browser.
Source maps address being able to debug with the code I wrote rather than the built code. These come for free with an app generated with Create React App.
The build process is still there, but it has little impact on your development workflow.
Sweet! I wasn't aware dev.to used Github markdown. Got that fixed. Thanks for the tip!