2

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
  • 1
    Swap rewardRef.reward.rewardMe(); to rewardRef.current.rewardMe() Commented Feb 1, 2021 at 20:55
  • this didn't work unfortunately, the error message is still the same: "TypeError: Cannot read property 'rewardMe' of undefined" Commented Feb 1, 2021 at 21:02

2 Answers 2

3

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();
};
Sign up to request clarification or add additional context in comments.

Comments

1

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();
      }; 
 

4 Comments

this mutes the error, but unfortunately nothing happens in the script :/...is the way I converted the ref correct? In the class component when I activate the onChange of the checkbox, it triggers the "rewardMe()" from the library, however using it the way in your answer just checks the box and nothing happens.
log the current object, and see what it has inside. See if you can call directly rewardMe() from current
I think for this one to be correct based off the class component you would want rewardRef.current?.rewardMe(); but as @JulianoHenrique stated you should be able to see whats going on if you just log rewardRef.current.
yeah you're right @JacobSmit! Your initial comment was also correct, I just had to add the nullish coalescing as suggested here. Thanks for your input!

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.