Should I move away from classes when using export classes with hooks?

Issue

This is a knowledge based question. I am new to React coming from a Angular TypeScript class based background. I want to continue using TypeScript when using React but having trouble with compiling hooks.

This code is from the create-react app library and uses a Redux template. It uses a counter component to display a counter.

import React, { useState } from 'react';

import { useAppSelector, useAppDispatch } from '../../app/hooks';
import {
  decrement,
  increment,
  incrementByAmount,
  incrementAsync,
  incrementIfOdd,
  selectCount,
} from './counterSlice';
import styles from './Counter.module.css';

export function Counter() {
  const count = useAppSelector(selectCount);
  const dispatch = useAppDispatch();
  const [incrementAmount, setIncrementAmount] = useState('2');

  const incrementValue = Number(incrementAmount) || 0;

  return (
    <div>
      <div className={styles.row}>
        <button
          className={styles.button}
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          -
        </button>
        <span className={styles.value}>{count}</span>
        <button
          className={styles.button}
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          +
        </button>
      </div>
      <div className={styles.row}>
        <input
          className={styles.textbox}
          aria-label="Set increment amount"
          value={incrementAmount}
          onChange={(e) => setIncrementAmount(e.target.value)}
        />
        <button
          className={styles.button}
          onClick={() => dispatch(incrementByAmount(incrementValue))}
        >
          Add Amount
        </button>
        <button
          className={styles.asyncButton}
          onClick={() => dispatch(incrementAsync(incrementValue))}
        >
          Add Async
        </button>
        <button
          className={styles.button}
          onClick={() => dispatch(incrementIfOdd(incrementValue))}
        >
          Add If Odd
        </button>
      </div>
    </div>
  );
}

This works as expected so I changed it to this

export default class Counter extends React.Component<unknown> {
  render(): {
    const count = useAppSelector(selectCount);
    const dispatch = useAppDispatch();
    const [incrementAmount, setIncrementAmount] = useState('2');

    const incrementValue = Number(incrementAmount) || 0;

    return (
      <div>
        <div className={styles.row}>
          <button
            className={styles.button}
            aria-label="Decrement value"
            onClick={() => dispatch(decrement())}
          >
            -
          </button>
          <span className={styles.value}>{count}</span>
          <button
            className={styles.button}
            aria-label="Increment value"
            onClick={() => dispatch(increment())}
          >
            +
          </button>
        </div>
        <div className={styles.row}>
          <input
            className={styles.textbox}
            aria-label="Set increment amount"
            value={incrementAmount}
            onChange={(e) => setIncrementAmount(e.target.value)}
          />
          <button
            className={styles.button}
            onClick={() => dispatch(incrementByAmount(incrementValue))}
          >
            Add Amount
          </button>
          <button
            className={styles.asyncButton}
            onClick={() => dispatch(incrementAsync(incrementValue))}
          >
            Add Async
          </button>
          <button
            className={styles.button}
            onClick={() => dispatch(incrementIfOdd(incrementValue))}
          >
            Add If Odd
          </button>
        </div>
      </div>
    );
  }
}

But when I compile it – I get this very common error

Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug
    and fix this problem.

Quite obviously my code doesnt adhere to any of the error (which for once, it’s quite clear which pleases me). But does this mean when using hooks, I will now lose the export default class syntax?

For context, I used this code in my parent component (App) and it does compile.

import React from 'react';
import './App.css';
import Counter from './features/counter/Counter';

export default class App extends React.Component<unknown> {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <Counter />
           ...

Any info I’d be grateful. Thanks

****** EDIT *******

I see you can use High Order Components as explained here

How can I use React hooks in React classic `class` component?

which begs the question, is it wise to use the class based syntax for React now?

Additional Reading based on the answer below: https://www.twilio.com/blog/react-choose-functional-components

Solution

Revert to what you had before, hooks are only usable inside React Function components.

// Correct, function component
export function Counter() {

// Incorrect, class component
export default class Counter extends React.Component<unknown> {
  render(): {

The linting issue is #2, "breaking the Rules of Hooks". Specifically this is violating Only Call Hooks from React Functions (your second example is a React Class not a React Function).

Answered By – Ross Allen

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published