Cut Into The Jamstack book logo

Cut Into TheJamstack

Modern React Component Structure

Mike Cavaliere

10/3/2021

Nowadays in React, we use functional components with hooks instead of class-based components. Functional components are the recommended way to write components going forward, and luckily they're even simpler to use.

Why use functional components in React?

Other than being the official recommendation of the React development team, functional components have a number of benefits over traditional class-based React components:

  1. They're easier to read and understand.
  2. They're easier to write.
  3. They're easier to test.
  4. They compose more cleanly.
  5. In the future, they'll run faster.

But don't take my word for it; let's try some out.

How to write a React functional component

A functional component is simply a function that does two things, at minimum:

  1. Accepts some props as parameters
  2. Returns JSX to render.

Here's the most trivial example.

function MyButton({ children, onClick }) {
  return <button onClick={onClick}>{children}</button>;
}

Alternatively this can be written as an anonymous arrow function:

const MyButton = ({ children, onClick }) => {
  return <button onClick={onClick}>{children}</button>;
}

Or even more concisely without using the return statement:

const MyButton = ({ children, onClick }) => (<button onClick={onClick}>{children}</button>);

All of these achieve the same result; a component that can be called like this:

<MyButton onClick={() => alert("Clicked!")}>Click me!</MyButton>

Here's a simple example on CodeSandbox.

A functional component with TypeScript

Adding TypeScript typings is easy. Here's the same example with TypeScript:

import "./styles.css";

type MyButtonProps = {
  children: string;
  onClick: () => void;
};

const MyButton = ({ children, onClick }: MyButtonProps): JSX.Element => (
  <button onClick={onClick}>{children}</button>
);

export default function App() {
  return (
    <div className="App">
      <h1>Hello CutIntoTheJamstack</h1>
      <MyButton onClick={() => alert("Clicked")}>Click me!</MyButton>
    </div>
  );
}

Here's the same example on CodeSandbox.

Adding functionality with hooks

Hooks can be used to do anything class-based components can do. For example managing state inside a component. The useState hook replaces the setState function:

import { useState } from 'react';

const ToggleButton = ({ children, onClick }) => {
  const [active, setActive] = useState(false);

  const style = {
    backgroundColor: active ? 'tomato' : 'gray'
  }

  return (
    <button onClick={onClick} style={style}>
      {children}
    </button>
  )
}


Similarly for side effects (things like API calls, other async behavior, etc), in place of lifecycle methods like componentDidMount we now use the useEffect hook.

import { useEffect, useState } from 'react';

const RandomListOfText = () => {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/posts')
      .then(response => response.json())
      .then(data => setPosts(data))
  }, [])

  return (
    <ul>
      {posts.map(p => <li>{p.title}</li>)}
    </ul>
  )
}

In addition to the many hooks included with React, you can define your own custom hooks and pull in others from npm packages others have already created.

Testing functional React components

The testing process becomes easier and cleaner as well, especially when using React Testing Library:

import { render, fireEvent } from "@testing-library/react";
import { MyButton } from "./MyButton";

describe("MyButton", () => {
  it("should render", () => {
    const onClick = jest.fn();
    const { getByText } = render(<MyButton onClick={onClick}>Foobar</MyButton>);

    const button = getByText("Foobar");
    expect(button).toBeDefined();

    fireEvent.click(button);
    expect(onClick).toHaveBeenCalled();
  });
});


Another sandbox here.

Supported by

eb-logo-gradient-black-textCreated with Sketch.

© 2022 Mike Cavaliere. All rights reserved

TwitterMediumLinkedIn