Error rewriting as a React Functional Component in Typescript: Property 'forceUpdateHandler' does not exist on type 'MutableRefObject<Spinner | null>'

Issue

I’m trying to rewrite the App component in this CodePen as a Functional component using Typescript.

However, I am getting error like this when trying to run it:

ERROR in src/App.tsx:13:14

TS2339: Property 'forceUpdateHandler' does not exist on type 'MutableRefObject<Spinner | null>'.
    11 |
    12 |   const handleClick = () => {
  > 13 |     _child1?.forceUpdateHandler();
       |              ^^^^^^^^^^^^^^^^^^
    14 |     _child2?.forceUpdateHandler();
    15 |     _child3?.forceUpdateHandler();
    16 |   };

What is the correct way to handle Spinner.forceUpdateHandler?


Here’s my attempt:

App.tsx

Rewriting the class component as a functional component
This has been simplified from the original to focus on the problematic area

import React, { useRef } from "react";
import "./App.css";
import Spinner from "./Spinner.js";

const App = () => {
  const [matches, setMatches] = React.useState<number[]>([]);

  const _child1 = useRef<Spinner | null>(null);
  const _child2 = useRef<Spinner | null>(null);
  const _child3 = useRef<Spinner | null>(null);

  const handleClick = () => {
    _child1?.forceUpdateHandler();
    _child2?.forceUpdateHandler();
    _child3?.forceUpdateHandler();
  };

  const finishHandler = (value: number) => {
    setMatches([...matches, value]);
    if (matches.length === 3) {
      console.log("Done");
      emptyArray();
    }
  };

  const emptyArray = () => {
    setMatches([]);
  };

  return (
    <div>
      <div className={`spinner-container`}>
        <Spinner onFinish={finishHandler} ref={_child1} timer="1000" />
        <Spinner onFinish={finishHandler} ref={_child2} timer="1400" />
        <Spinner onFinish={finishHandler} ref={_child3} timer="2200" />
        <div className="gradient-fade"></div>
      </div>
      <button onClick={handleClick}>SPIN!!!</button>
    </div>
  );
};

export default App;

Spinner.js

Same as in the above CodePen, with imports and exports added

import React from "react";

class Spinner extends React.Component {
  constructor(props) {
    super(props);
    this.forceUpdateHandler = this.forceUpdateHandler.bind(this);
  }

  forceUpdateHandler() {
    this.reset();
  }

  reset() {
    if (this.timer) {
      clearInterval(this.timer);
    }

    this.start = this.setStartPosition();

    this.setState({
      position: this.start,
      timeRemaining: this.props.timer,
    });

    this.timer = setInterval(() => {
      this.tick();
    }, 100);
  }

  state = {
    position: 0,
    lastPosition: null,
  };
  static iconHeight = 188;
  multiplier = Math.floor(Math.random() * (4 - 1) + 1);

  start = this.setStartPosition();
  speed = Spinner.iconHeight * this.multiplier;

  setStartPosition() {
    return Math.floor(Math.random() * 9) * Spinner.iconHeight * -1;
  }

  moveBackground() {
    this.setState({
      position: this.state.position - this.speed,
      timeRemaining: this.state.timeRemaining - 100,
    });
  }

  getSymbolFromPosition() {
    let { position } = this.state;
    const totalSymbols = 9;
    const maxPosition = Spinner.iconHeight * (totalSymbols - 1) * -1;
    let moved = (this.props.timer / 100) * this.multiplier;
    let startPosition = this.start;
    let currentPosition = startPosition;

    for (let i = 0; i < moved; i++) {
      currentPosition -= Spinner.iconHeight;

      if (currentPosition < maxPosition) {
        currentPosition = 0;
      }
    }

    this.props.onFinish(currentPosition);
  }

  tick() {
    if (this.state.timeRemaining <= 0) {
      clearInterval(this.timer);
      this.getSymbolFromPosition();
    } else {
      this.moveBackground();
    }
  }

  componentDidMount() {
    clearInterval(this.timer);

    this.setState({
      position: this.start,
      timeRemaining: this.props.timer,
    });

    this.timer = setInterval(() => {
      this.tick();
    }, 100);
  }

  render() {
    let { position, current } = this.state;

    return (
      <div
        style={{ backgroundPosition: "0px " + position + "px" }}
        className={`icons`}
      />
    );
  }
}

export default Spinner;

Solution

Ref‘s hold the actual reference in the current property, so it should actually be:

  const handleClick = () => {
    _child1?.current?.forceUpdateHandler();
    _child2?.current?.forceUpdateHandler();
    _child3?.current?.forceUpdateHandler();
  };

You can read more about it here

Answered By – yorch

Answer Checked By – Willingham (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.