Converting class component to functional component using react hooks (useRef)—how to avoid "undefined" error

Viewed 300

I'm trying to convert this code snippet which works as a class component to a functional component (using the react-rewards library).

Class component (working):

import { Checkbox } from "@chakra-ui/react";
import React, { Component } from "react";
import Reward from "react-rewards";


  const config = {
    fakingRequest: false,
    angle: 90,
    decay: 0.91,
    spread: 100,
    startVelocity: 20,
    elementCount: 15,
    elementSize: 20,
    lifetime: 200,
    zIndex: 10,
    springAnimation: true,
    rewardPunish: "reward",
    type: "emoji",
  };
 

   export default class Playground extends Component {

     fakeRequest = () => {
         console.log(this);
         //@ts-ignore
         this.reward.rewardMe()
     };

     render() {
        const {
          type,
          fakingRequest,
          lifetime,
          angle,
          decay,
          spread,
          startVelocity,
          elementCount,
          elementSize,
          zIndex,
          springAnimation,
          rewardPunish,
        } = config;

       return (
         <div className="container">
           <Reward
             ref={(ref) => {
               //@ts-ignore
               this.reward = ref;
             }}
             type={"emoji"}
             config={{
               lifetime,
               angle,
               decay,
               spread,
               startVelocity,
               elementCount,
               elementSize,
               zIndex,
               springAnimation,
             }}
           >
             <Checkbox
               defaultChecked={false}
               onChange={this.fakeRequest}
             />
           </Reward>
         </div>
       );
     }
   }

This is what I have so far in the functional component, but it's not working, (the exact error message is: "TypeError: Cannot read property 'rewardMe' of undefined"):

import { Checkbox } from "@chakra-ui/react";
import React, { Component } from "react";
import Reward from "react-rewards";

const FPlayGround: React.FC = () => {

    const rewardRef = React.useRef();

    const config = {
        fakingRequest: false,
        angle: 90,
        decay: 0.91,
        spread: 100,
        startVelocity: 20,
        elementCount: 15,
        elementSize: 20,
        lifetime: 200,
        zIndex: 10,
        springAnimation: true,
        rewardPunish: "reward",
        type: "emoji"
    };

      const fakeRequest = (rewardRef) => {
        rewardRef.reward.rewardMe();
      };

  return (
    <>
      <div className="container">
        <Reward ref={rewardRef} type={"emoji"} config={config}>
          <Checkbox
            defaultChecked={false}
            onChange={fakeRequest}
          ></Checkbox>
        </Reward>
      </div>
    </>
  );
};
export default FPlayGround;

I think it has to do with the way I converted the "ref" portion, but I'm really not experienced enough to know for sure. Any help appreciated.

Thanks.

2 Answers

The Ref

const rewardRef = React.useRef();

This line here creates a ref object whose .current value is initially undefined. Because you didn't set any generic, typescript infers the value type based on the initial value and thus assumes that it will always be undefined. So you cannot set any value to this ref!

Type 'MutableRefObject<undefined>' is not assignable to type 'Ref<RewardElement>'.
Type 'MutableRefObject<undefined>' is not assignable to type 'RefObject<RewardElement>'.
Types of property 'current' are incompatible.
Type 'undefined' is not assignable to type 'RewardElement | null'.ts(2322)

The error on the ref actually tells us a bunch of information.

  • We want a ref for a RewardElement
  • Our initial value should be null instead of undefined

So we change our ref hook to this:

const rewardRef = React.useRef<RewardElement>(null);

We can get that type RewardElement from your imports (if it wasn't imported, you would use typeof Reward).

import Reward, {RewardElement} from "react-rewards";

The Callback

The onChange handler for a Checkbox receives a change event as its argument. We are not using that event, so we will use a function with zero arguments. We do not want the ref to be an argument of the callback because that is not what it will be called with.

The value of a ref is stored on the property .current. We initialized this to null, so rewardRef.current will either be a RewardElement or null. We do not want to call .rewardMe() on null so that's why we use the nullish coalescing operator ?. that others have suggested.

const fakeRequest = (): void => {
    rewardRef.current?.rewardMe();
};

the actual properties of a DOM item you can catch with property "current" of ref:

 const fakeRequest = (rewardRef) => {
        rewardRef.current.reward.rewardMe();
      };

even if the object referenced is too deep, like: myBigObjRef.current.something.anothersomething

you can use nullish coalescing

const fakeRequest = (rewardRef) => {
        rewardRef.current?.reward?.rewardMe();
      }; 
 
Related