Two (More) Ways to Get Hooked on React

Making cleaner code and simpler components helps keep errors from hiding, and increases the reusability of each component. Hooks were announced in 2018's React conference, and have been an absolute game changer for React development! Not only can we start shedding class lifecycle methods and make more functional components, Hooks help make our code more compact, readable, and reusable.

React Conference 2018 - When Hooks were released

Simplify existing components

Take one of the simpler examples, a button that keeps a state of how many times we click it. In React 16.7, we might write something like this:

class ButtonInstance extends React.Component<{}, ButtonInstanceState> {
  constructor() {
    super();
    this.state = {
      pressedCounter: 0
    };
  }

  onPress = () => {
    this.setState((prevState) => ({
      pressedCounter: prevState.pressedCounter + 1
    }));
  };

  render() {
    let { pressedCounter } = this.state;

    return (
      <ClickTrackerButton pressed={pressedCounter} onPress={this.onPress} />
    );
  }
}
Button Container
const ClickTrackerButton = ({
  pressed,
  onPress
}: ClickTrackerButtonProps): ReactElement => {
  let buttonStyle = {
    width: "30%"
  };

  return (
    <div>
      <button style={buttonStyle} onClick={onPress}>
        Clicked {pressed} times
      </button>
    </div>
  );
};
Button Presentation
interface ClickTrackerButtonProps {
  pressed: number;
  onPress: any;
}

interface ButtonInstanceState {
  pressedCounter: number;
}

ReactDOM.render(<ButtonInstance />, document.getElementById("root"));
Supporting interfaces and React mounting point

We created two components:

  • ClickTrackerButton, Our presentation component. The button will display how many times it has been pressed, and when the button is pressed, it will send a signal to the onPress prop that was passed to it.
  • ButtonInstance, Our Container component, the source of state for the component, and a way to update state.

In this example, we broke apart the display and state management to illustrate a design pattern I strongly recommend, the Presentation / Container pattern. This will help improve the reuse and testability of the components. Rendering a ButtonInstance looks fairly straightforward, as-is, in React 16.7, but what if we migrate it to use Hooks (React >= 16.8)?

function ButtonInstance(): ReactElement {
  const [pressedCounter, setCounter] = React.useState<number>(0);

  const onPress = () => setCounter(prevCounter => prevCounter + 1);

  return <ClickTrackerButton pressed={pressedCounter} onPress={onPress} />;
}

It uses the useState() hook, setting the initial value to zero, and creates two variables, a pressedCounter to store the current state and a setCounter method to update the state of pressedCounter. Because we are incrementing the previous value, we still need to use a lambda to reference the old value and never use pressedCounter directly in the setting method.

What are the major differences?

  • There's significantly less code for bugs to hide.
  • We don't need a class for ButtonInstance, so much of the syntactic sugar around the constructor and lifecycle methods that aren't part of the business logic can be removed.
  • We can encapsulate the entire container in 3 lines of clean typescript.
  • We no longer need a state interface and can still keep the button clicks strongly typed.

UseEffect hook

Taking our initial ButtonInstance, and adding a DOM manipulation, we would add two lifecycle methods, some handling code, and end up with this being added to the class:

  componentDidMount() {
    this.titleUpdate();
  }
  
  componentDidUpdate() {
    this.titleUpdate();
  }
  
  const titleUpdate = () => {
     document.title = `Button was pressed ${this.state.pressedCounter} times`;
  }

We added the ComponentDidMount and ComponentDidUpdate lifecycle methods to the class, as well as the handling code. Doing this with Hooks is just one small add to the existing code adjusting the document title:

  React.useEffect(() => {
    document.title = `Button was pressed ${pressedCounter} times`;
  });

Where to go from here

This is just two of the ways we can successfully leverage Hooks to clean up our components, and help make better React code. Check out the Official Documentation and start using Hooks today!