Building Blocks of Function Components in React

Jul 6, 2020

Programming Tutorial

By: Brandon Quakkelaar

Class based components aren’t the only way to build rich user interfaces in React. Here are some building blocks that can be used to build the same dynamic experiences using Function Components.

Key JavaScript Concepts

JavaScript Arrow Functions

Since React apps use a build system to generate the runtime code, we’re able to take advantage of ES6 syntax via transpilers without needing to worry about browser support. This gives us access to Arrow Functions syntax which are lambda functions in JavaScript.

Before ES6 was available JavaScript had anonymous functions.

var anonFunc = function (name) { return "Hello " + name; }

Since ES6 is available we can rewrite the anonymous function above as an Arrow function like this:

const arrowFunc = (name) => { return "Hello " + name; }

First thing that’s different is you’ll notice the const keyword instead of var. The const keyword defines this as a constant value and doesn’t allow arrowFunc to get redefined elsewhere. It’s available to us in ES6 and it’s usually more appropriate for declaring functions.

Next, notice the absence of the function keyword, and instead we see => which is the ‘arrow’ that indicates an Arrow function. (This should be very familiar to C# programmers because their lambda syntax is very similar.) The function parameters are listed inside paranthesis on the left of the arrow, and the function body is on the right.

If there’s only one parameter expected, then Arrow function definitions can skip the paranthesis.

const arrowFunc = name => { return "Hello " + name; }

Another convenient feature of Arrow functions is the implicit return that occurs if we remove the curly braces.

const arrowFunc = name => "Hello " + name

The result is syntax that can be fairly terse and clean, but if you’re returning an object literal then you have to add paranthesis around it to ensure it’s interpretted as a return value correctly.

// doesn't work
const objLiteral = () => {one: 1}

// returns the object
const objLiteral = () => ({one: 1})

Closures

JavaScript has had closures since the beginning. It’s one of the most powerful aspects of the language.

Essentially, it just means that functions have access to their surrounding scope (i.e. lexical environment.)

let canSeeMe = true;
const run = () => {
  let canSee = true;
  const displayCanSee = () => {
    console.log(canSee);
    console.log("and")
    console.log(canSeeMe);
  }
  displayCanSee();
}
run();

In the example above the displayCanSee function can see the canSee and canSeeMe variables even though they’re outside of the scope of the display function.

JavaScript Object Destructuring

This is another JavaScript feature introduced in ES6. The assignment aspect is a useful shortcut for creating named variables using values from objects.

const person = {
  first: 'George',
  last: 'Washington',
};

const {first, last} = person; // destructuring 

console.log(first); // prints 'George'
console.log(last); // prints 'Washington'

Notice the curly braces on the left side if the assignment operator, =. When you see that, recognize it’s using object destructuring to assign property’s values to individual variables.

Key React Concepts

Programming React components without the class based syntax has been possible for some time. But such function based components were limited from being able to use state. That is, they were until React version 16.8 when a new API was introduced. React hooks allow a function component to be stateful.

Here are examples of a function component using the JavaScript features we’ve already examined.

// traditional function declaration
function Hello(props) {
  return <h1>Hello {props.name}</h1>
}

// arrow function
const Hello = props => {
    return <h1>Hello {props.name}</h1>
}

// implicit return
const Hello = props => <h1>Hello {props.name}</h1>

// object destructuring
const Hello = {name} => <h1>Hello {name}</h1>

Now let’s add some state.

The useState Hook

State is for values that are displayed to the user which are subject to change. In the coming example we’ll be showing a default message to the user and the user will be able to change the message to a word or phrase of their choosing.

The useState hook can be imported like this.

import React, { useState } from 'react';

Its usage looks like this.

const [msg, setMsg] = useState('Hello world');

Let’s break this down. First, this line is using array destructuring (same principles as object destructuring covered above) to handle the return value from the useState function call. A default value of “Hello world” is passed in, and we get two values out. msg which is set to “Hello world”, and setMsg which is a function that we can use to update the value of msg.

React beginners might wonder why they need the setMsg function. They could certainly just use an assignment operator to update msg’s value, but the trick is getting the new value displayed to the user. The new value won’t be updated in React unless we use setMsg.

Now, here’s how useState can be used to dynamically update a function component.

const Hello = () => {
  const [msg, setMsg] = useState('Hello world');

  return (
    <>
      <h1>{msg}</h1>
      <input 
        type="text" 
        onChange={e => setMsg(e.target.value)} />
    </>
  );
}

Now we’re using JavaScript closure feature. Notice the onChange event of the input is being bound to another arrow function. That arrow function, via closure, has access to the setMsg function we set in the lexical scope. When the user changes that input then the default value of “Hello world” will immediately be updated and displayed as he types.

Here’s a working demo.

The useEffect Hook

useEffect is for performing side effects whenever a component changes. It’s similar to the class based approach that uses componentDidMount, componentDidUpdate, and componentWillUnmount methods. It replaces all three.

One of the most common usages of useEffect is for loading data via API calls.

The useEffect hook can be imported by adding it to our previous import statement.

import React, { useState, useEffect } from 'react';

Here’s a very simple useage to illustrate a side effect, though not a very useful one.

const Hello = ({ name }) => {
  
  const [displayName, setDisplayName] = useState(name);

  useEffect(() => {
    setDisplayName('George') // side effect
  });
  
  return <>
    <h1>Hello {displayName}!</h1> {/* Hello George */}
  </>
};

The previous component receives a name property on the props object and assigns it to as the default value of displayName which is what gets shown to the user. But, since we’ve create a side effect that always sets displayName to “George”, the user will never see any name other than “George”.

Let’s move on to a more useful example. Side effects are often used to take data from an API call and display it to the user.

Here’s some quick code for calling Bible API.

const api = {
  getPassage: search => {
    return axios.get(`http://bible-api.com/${search}`).then(r => r.data);
  }
}

Our function component will have two variables in state.

  const [passage, setPassage] = useState('');
  const [search, setSearch] = useState('Ephesians 2:8-10');

Then, we have a very simple side effect that calls the API.

  useEffect(() => {
    api.getPassage(search).then(d => setPassage(d.text));
  });

The useEffect function lets React know about our arrow function that contains the side effect. So whenever this component mounts or is updated, the side effect code will execute.

Here’s the complete code.

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const api = {
  getPassage: search => {
    return axios.get(`http://bible-api.com/${search}`).then(r => r.data);
  }
}

const SearchBar = ({defaultSearch, onClick}) => {
  const [inputSearch, setInputSearch] = useState(defaultSearch);
  
  return (
    <>
      <input 
        type='text' 
        value={inputSearch} 
        onChange={e => setInputSearch(e.target.value)} />
      <button 
        onClick={() => onClick(inputSearch)}>
        Search
      </button>
    </>
  )
};

const Hello = () => {
  const [passage, setPassage] = useState('');
  const [search, setSearch] = useState('Ephesians 2:8-10');

  useEffect(() => {
    api.getPassage(search).then(d => setPassage(d.text));
  });

  return (
    <>
      <p>"{passage}"</p>
      <p style={{textAlign:'right'}}>- {search}</p>
      <SearchBar
        defaultSearch={search} 
        onClick={(value) => setSearch(value)} />
    </>
  );
};

export default Hello;

And, here’s the useEffect example in a sandbox.

One thing to notice in the code example; the search bar is separated into it’s own function component. The reason it’s separate from the Hello component is because every time a user presses a key in that input box, then the component is updated. If the input were inside the Hello component, then every keypress would trigger a new call out to the API. We don’t want that. We avoid it by putting the input and the corresponding button elements into a SearchBar component of their own.

Wrapping Up

We can see how these features build on each other to get rich function components in React. Closures and arrow functions combined with object/array destructuring are used in concert with React Hooks to deliver a full user experience without needing any of the class overhead found with traditional class based components. There are no plans to remove tradition class based components, but it’s still good to explore and understand function components because you might prefer the style, you might come across function components that you need to support, or both.

Further Reading