0

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;
2
  • Spinner.forceUpdateHandler is defined in Spinner, which remains unchanged as a class component. _child1 is a ref that points to a Spinner component instance Commented Jun 9, 2022 at 4:23
  • you're right i read the code too fast, anyway in the functionnal component when you write useRef<Spinner | null>(null); Spinner is actually the class itself, and not its typescript defintion Commented Jun 9, 2022 at 4:26

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

Comments

0

just an idea may be you should try redefining the Spinner type in your functional component

type SpinnerProps ={
 forceUpdateHandler: () => void,
 startPosition: () => number,
 .... // TODO all the other props need to be defined 
}

const _child1 = useRef<SpinnerProps | null>(null);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.