React: Functional Components vs Class Components

Intro

Functional Components are good for simple components.

Class Components are good for everything else.

The benefits of Class components are:

  • Easier code organization (subjectively)
  • Can use ‘state’ (another React system)
  • Easier to handle user input
  • Understands lifecycle events
  • Easier to do things when the app first starts

Let’s look at an example of when a Class Component would be necessary. Take this App:

import React from "react";
import ReactDOM from "react-dom";

const App = () => {
  window.navigator.geolocation.getCurrentPosition(
    (position) => console.log(position),
    (err) => console.log(err)
  );
  return <div> Latitude:</div>;
};

ReactDOM.render(<App />, document.querySelector("#root"));

In this example we are using the browsers built in geolocation function to ping the location of the user and then render their Latitude on the screen. However the problem is that it takes some amount of time to retrieve the user location, and the application moves immediately to the return function before the browser has had a chance to retrieve the location. Just like an instance where we would need to Async Await the result of a function before moving on. Here we need to use Class Components to accomplish this successfully. In this case we need to be able to tell the component to re-render itself once we get the result of the geolocation.

Rules of Class Components

  • Must be a Javascript Class
  • Must extend (subclass) React.Component
  • Must define a ‘render’ method that returns some amount of JSX

So keeping all that in mind we can refactor our App with the following class component:

import React from "react";
import ReactDOM from "react-dom";

class App extends React.Component {
  render() {
    window.navigator.geolocation.getCurrentPosition(
      (position) => console.log(position),
      (err) => console.log(err)
    );
    return <div> Latitude: </div>;
  }
}

ReactDOM.render(<App />, document.querySelector("#root"));

Functionally this is the same, we still won’t see the actual latitude on the screen yet. But now we can get into the concept of States.

States

Rules of the state system:

  • Only usable with class components
  • You will confuse props with state!
  • ‘State’ is a JS object that contains data relevant to a singular component
  • Updating ‘state’ on a component causes the component to almost instantly rerender
  • State must be initialized when a component is created
  • State can only be updated using the function ‘setState’

Initializing State (Constructor)

Here are some refresher docs on the constructor method.

ncoughlin: Constructor and Object Oriented Programming MDN: Constructor

class App extends React.Component {
  // javascript class that initializes state
  constructor(props) {
    super(props);
  }

  // react requires us to define render
  render() {
    window.navigator.geolocation.getCurrentPosition(
      (position) => console.log(position),
      (err) => console.log(err)
    );
    return <div> Latitude: </div>;
  }
}

Super

The App component is extending (borrowing functionality) from the React.Component base class. The base class has a constructor function of it’s own that goes through some amount of setup to setup a React Component for us. When we define a constructor function inside of our App class, we are overriding the constructor function inside of the React.Component class. But we still want to make sure that all the setup code inside the React.Component constructor function still gets called. So to make sure that the parents (React.Component) construction function gets called we call super with props.

Super is a reference to the parents (React.Component) constructor function.

Initial State

Next we need to set the initial state inside of our constructor, which conventionally we would start as null.

this.state = { lat:null };

Which we would then reference inside of the render function

return <div> Latitude: {this.state.lat}</div>;

So all together that would be

class App extends React.Component {
  // javascript class that initializes state
  constructor(props) {
    super(props);

    this.state = { lat:null };
  }

  // react requires us to define render
  render() {
    window.navigator.geolocation.getCurrentPosition(
      (position) => console.log(position),
      (err) => console.log(err)
    );
    return <div> Latitude: {this.state.lat}</div>;
  }
}

Updating State Property

First we want to separate our DOM elements from the data that we are trying to push into them. Let’s move the geolocation request up to the constructor that is managing the state.

class App extends React.Component {
  // javascript class that initializes state
  constructor(props) {
    super(props);

    this.state = { lat:null };

    window.navigator.geolocation.getCurrentPosition(
      (position) => console.log(position),
      (err) => console.log(err)
    );
  }

  // react requires us to define render
  render() {
    return <div> Latitude: {this.state.lat}</div>;
  }
}

This is where .setState comes in to update the state. Inside our callback for the retrieve location function we set the state of lat like so.

class App extends React.Component {
  // javascript class that initializes state
  constructor(props) {
    super(props);

    this.state = { lat:null };

    window.navigator.geolocation.getCurrentPosition(
      (position) => {
        // we called setstate!
        this.setState({ lat: position.coords.latitude});
      },
      (err) => console.log(err)
    );
  }

  // react requires us to define render
  render() {
    return <div> Latitude: {this.state.lat}</div>;
  }
}

It’s important to know that the only time we explicitly set the state (with =) is when we declare it. All other times that we update the state of the component we use .setState.

To update the status of multiple properties you would just chain them together in the object like so

class App extends React.Component {
  // javascript class that initializes state
  constructor(props) {
    super(props);

    this.state = { lat: null, long: null };

    window.navigator.geolocation.getCurrentPosition(
      (position) => {
        // we called setstate!
        this.setState({
          lat: position.coords.latitude,
          long: position.coords.longitude,
        });
      },
      (err) => console.log(err)
    );
  }

  // react requires us to define render
  render() {
    return (
      <div className="ui container">
        <div className="ui card">
          <div className="content">
            <div className="header">Current Position:</div>
            <div className="description">Latitude: {this.state.lat}</div>
            <div className="description">Longitude: {this.state.long}</div>
          </div>
        </div>
      </div>
    );
  }
}

position showing