Why should I care about React?

Devon Campbell - Nov 25 '18 - - Dev Community

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";
Enter fullscreen mode Exit fullscreen mode

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 />
Enter fullscreen mode Exit fullscreen mode

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);
  });
});
Enter fullscreen mode Exit fullscreen mode

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"));

Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .