Daniel Eden, Designer

Anatomy of a Typed React Component

It’s no secret that learning JavaScript (and React) in 2020 is confusing at best and downright intimidating at worst. Multiple technologies, tools, and approaches tend to get bundled together in React tutorials, and if you haven’t written JavaScript for a few years, it can feel like a totally new landscape.

In this post, I wanted to take a look at a very simple React component and walk step-by-step through the parts of the code that are unique to either:

Here’s what we’ll be looking at, with some markers/annotations in place:

// |--- 1 ---|    |--------- 2 ---------||-- 3 --|| 4 |
const Component = ({ children, hasPadding }: Props) => {
    return (
        // |--- 5 ---| |---------- 6 ----------|
        <div className={hasPadding && "padding"}>{children}</div>
    )
}

This post assumes some level of competancy with JavaScript, and some of you may be familiar with the concepts ahead. If you notice anything erroneous in my explanations, feel free to suggest changes.

Table of Contents

Constants

const Component

The first marker in our code highlights a keyword that may be new to you: const.

Constants in JavaScript are essentially variables which, once assigned, cannot be changed. In the past you may have written or seen code that looks like this:

var number = 1
number = 2

console.log(number) // => 2

There are sometimes cases where you need to change a variable after it’s been assigned; but most of the time, once its been set, you don’t need to change it. In fact, leaving it open to change can result in unexpected results. This is where constants can come in handy.

const number = 1
number = 2 // Uncaught TypeError: Assignment to constant variable

Nowadays, many people follow a rule of “always prefer const”. If you do need to assign a variable that may need to change later, let is another keyword that is preferred over var:

let number = 1
number = 2

console.log(number) // => 2

There are some more differences worth learning about between var, let, and const, and plenty of material already written on the subject.

Arrow Functions

Let’s keep on going through that first line, but omitting the Props part for now.

({ children, hasPadding }) => { ... }

This is a relatively new expression pattern called arrow functions. You may be used to seeing or writing functions like this:

function add(x, y) {
    return x + y
}

add(2, 2) // => 4

Arrow functions are “syntactical sugar” that lets you create functions without the need to use the function keyword. We can rewrite the above add function using arrow functions like this:

const add = (x, y) => x + y

For simple functions, like add, arrow functions can be a little easier to read since it lets you see the arguments and returned value in just one line. (Notice also that we can omit the {} curly braces and return keyword for simple expressions.)

One important thing to note about arrow functions is that they come without bindings to the this value. The this value is essentially a value representing the context from which a function or expression is called, and arrow functions somewhat skip a level:

const myObject = {
    foo: 1,
    // A regular function
    withThis: function () {
        console.log(this)
    },
    // An arrow function
    withoutThis: () => {
        console.log(this)
    },
}

myObject.withThis() // logs myObject
myObject.withoutThis() // logs Window

For the most part, this shouldn’t have much of an impact on your day-to-day JavaScript, but it’s worth calling out as the cause of some occasional bugs when dealing with functions.

Destructuring Assignment

Something new is going on in the arguments for that arrow function:

// |------ here -------|
({ children, hasPadding }) => { ... }

This expression is called a “destructuring assignment”, and it makes a bit more sense in a slightly different form:

(props) => {
    const { children, hasPadding } = props
    ...
}

You likely already know that curly braces in JavaScript can be used to initialize an Object. What you’re seeing above isn’t altogether different; what this expression is doing is assigning the properties children and hasPadding of the object props to new variables with the same name. It’s the same as writing this:

const props = {
    children: "A string value",
    hasPadding: true,
}

const children = props.children
const hasPadding = props.hasPadding

The syntax also works for arrays:

const myArray = [1, 2, 3]

const [first, second, third] = myArray

// The above line could also be written as:
const first = myArray[0]
const second = myArray[1]
const third = myArray[2]

Like with arrow functions, destructuring assignment is a convenient syntactical sugar that helps us write less, and often (though not always) more readable, code.

One more thing to know about destructuring assignment is that you can assign any remaining values in an object or an array using what’s called a spread operator. It looks like this:

const myProfile = {
    name: "Daniel Eden",
    age: 29,
    bio: "Designer, writing and thinking about design systems",
    favouriteDessert: "Sticky toffee pudding",
    tags: ["design", "london", "bread"],
}

const { name, age, ...rest } = myProfile
console.log(rest) // { bio, favouriteDessert, tags }

const [firstTag, ...tags] = rest.tags
console.log(tags) // ["london", "bread"]

Type Signatures

Let’s look at one last part of the first line of our React component, indicated at marker 3 in our code snippet at the top:

//                                        |-- 3 --|
const Component = ({ children, hasPadding }: Props) => {

This is the first non-native-JavaScript part of our code so far! Everything you’ve seen up to this point is totally valid, modern JavaScript. What you’re seeing here is called a type expression, and for JavaScript to understand it, the code would first need to be run through a type checker, such as Flow or TypeScript.

I’ve written a little about typed programming before, but here’s a brief primer for those unfamiliar. When code is “typed”, it doesn’t mean “typed with a keyboard”, but that the values that are created and passed around a program have expected types that describe the kind of values they represent.

Here are some example type expressions:

const myNumber: number = 42
const myString: string = "Hello"

What this tells the type checker (and the people reading or editing this code) is that the variable myNumber should be a number type, and myString should be a string type. We can intentionally introduce an error into this code:

const myNumber: string = 42
// TypeError: Type '42' is not assignable to type 'string'.

The type checker would helpfully let us know that the value 42 is incompatible with the requested type string.

Looking back at our add function from the arrow functions section, we can take a look at how types can help us write safer code. For example, it doesn’t make sense for the add function to add together a string and a number:

add(2, 2) // => 4
add(3, "4") // => "34"

Introducing type signatures to our code can help us avoid this error:

const add = (x: number, y: number): number => x + y

add(2, 2) // 4
add(3, "4") // TypeError: Argument of type '"4"' is not assignable to parameter of type 'number'.

Let’s take a look back at our React component.

const Component = ({ children, hasPadding }: Props) => { ... }

From the type signature, we can see that the object containing the properties children and hasPadding should be an object matching the Props type. If we rewrite the code so it doesn’t use a destructuring assignment, that may be a bit easier to see:

const Component = (props: Props) => { ... }

I haven’t included the type definition for Props yet, but let’s imagine it looks something like this:

interface Props {
    children?: React.ReactNode
    hasPadding: Boolean
    onClick?: () => void
}

The Props definition tells me that the argument passed to Component should be an object with the following properties:

As you can see, the Props definition has more information about the properties passed to the component. Not only does this help me understand the component a little more, but TypeScript will also warn me if I use the component incorrectly:

;<Component onClick={false} />
/**
 * TypeError: type '{ onClick }' is missing the following properties: children, hasPadding
 * TypeError: type 'false' is not assignable to type '(() => void) | undefined' in 'onClick'
 **/

We’re skipping ahead here, but trying to use Component in React without giving values for children or hasPadding shows an error, and we have another error for the value of onClick.

React and JSX

Whew, 1,500 words into this post and we’ve only covered a single line of code! Hopefully the explanations so far have helped you develop a better sense for how to understand more modern JavaScript code and typed JavaScript. Now, we’re going to get into the meat of React and a syntactical sugar introduced by React called JSX.

Let’s look again at our original Component definition:

const Component = ({ children, hasPadding }: Props) => {
    return (
        // |--- 5 ---| |---------- 6 ----------|
        <div className={hasPadding && "padding"}>{children}</div>
    )
}

The first two lines should now be familiar to you.

  1. In the first line we’ll instantiate a constant Component with a value that is a function. The function accepts a single argument with the type of Props. We’ll destructure that argument into two values, children and hasPadding.
  2. We’ll begin the return statement, which continues onto the next line(s)

And this is where things get interesting. Right away, we see <div> in the return value, which is odd. The < symbol is a logical operator in JavaScript, used to compare values—in this case, it checks that the left-hand value is less than the right-hand value. But in this case, there is no left-hand value, and div isn’t something we’ve defined—what’s going on?


You may recognise <div> as HTML code, which is a good hunch! What you’re seeing is another syntactical sugar, introduced by React, called JSX. JSX lets you write HTML-like code inside JavaScript.

When a code compiler encounters JSX, it gets transformed into a format that native JavaScript can understand, similar to how typed code needs to be run through a type checker first. In the case of JSX and React, the transformed code is a series of React functions. Let’s look at a rudimentary example.

const myJsxCode = <h1 className="greeting">Hello, world</h1>

const myTransformedCode = React.createElement(
    "h1", // 1
    { className: "greeting" }, // 2
    "Hello, world" // 3
)

These two variable assignments are identical. Let’s step through the arguments passed to React.createElement to understand how a code compiler transforms JSX.

  1. The first argument to React.createElement is the element name or reference. In this case, we’re using one of React’s built-in HTML elements, h1, which is passed as a string literal.
  2. The second argument is the properties or props argument. In the case of HTML elements, these are usually the attributes that are added, but props can be any value, as seen in our custom Props type definition earlier in the post.
  3. The third argument represents the children of the element. In this case, it’s just a string that says "Hello, world", but it could be more calls toReact.createElement to create nested elements.

Let’s rewrite that function call and arguments as JSX with annotations once more:

//                (1) (2)                   (3)
const myJsxCode = <h1 className="greeting">Hello, world</h1>

The children property is special insofar as it can either be passed in between the opening and closing tags for a React element, or it can be passed just like other properties. In other words, the code above can be rewritten as:

const myJsxCode = <h1 className="greeting" children="Hello, world" />

const myTransformedCode = React.createElement("h1", {
    className: "greeting",
    children: "Hello, world",
})

Now that we understand a bit about JSX, let’s look at our component again:

const Component = ({ children, hasPadding }: Props) => {
    return (
        // |--- 5 ---| |---------- 6 ----------|
        <div className={hasPadding && "padding"}>{children}</div>
    )
}

The part of the code indicated by marker 6 is a JSX embedded expression. It’s just a JavaScript expression written inside a JSX call by surrounding it in curly braces. In this case, hasPadding && "padding" is a boolean expression that checks if hasPadding is true and returns “padding”. If hasPadding is false, the whole expression will return false.

There’s another embedded expression surrounding children. This just tells JavaScript/JSX to output the contents of children.

Note that JSX embedded expressions can only be expressions—statements that produce a value. For example, you can’t put an if...else statement inside a JSX embedded expression since if...else statements don’t have to return a value. You can, however, use ternary operators to check a condition:

const Switch = ({ isOn }: { isOn: Boolean }) => (
    <div>The switch is {isOn ? "on!" : "off."}</div>
)

If you’re unfamiliar with ternary operators, they work like this:

condition ? {true value} : {false value}

You can expect to see them with some regularity in React code, since they’re useful for conditionally rendering different results based on a component’s properties.

Wrapping Up

What a journey! I hope you learned something new and that you’re feeling proud to have made it through this post. It’s amazing how many ideas and tools are wrapped up in just 5 lines of code. There’s a lot of additional material around to learn about each of these ideas in more detail—here’s just a sampling: