Modern Front-End Development for Rails, Second Edition: Chapter 3, Stimulus - elementToHideTarget is undefined (p 52)

On page 52, example code chapter_03/04/app/javascript/controllers/favorite_toggle_controller.ts, which is:

import { Controller } from "@hotwired/stimulus";

export default class FavoriteToggleController extends Controller {
  static targets = ["elementToHide"];
  elementToHideTarget: HTMLElement;

  toggle(): void {
    this.elementToHideTarget.classList.toggle("hidden");
  }
}

The way it is, will not work, since elementToHideTarget will always be undefined. To work I needed to make the following change:

import { Controller } from "@hotwired/stimulus";

export default class FavoriteToggleController extends Controller {
  static targets = ["elementToHide"];
  declare readonly elementToHideTarget: HTMLElement;

  toggle(): void {
    this.elementToHideTarget.classList.toggle("hidden");
  }
}

I did the same change on page 55:

static values = { visible: Boolean };
declare visibleValue: boolean;

Note that this time I did not declare it as readonly, since I am changing its value on flipState() function.

Versions:

Ruby 3.1.2
Rails: 7.0.4
Gem stimulus-rails: 1.1.0
@hotwired/stimulus: 3.1.0

What error do you get, and are you working from the supplied code or building it up yourself?

The elementToHideTarget should be set by Stimulus, it shouldn’t be undefined…

Thanks,

Noel

I am building it up myself. I think that there is a mismatch between the versions I am using and the ones you used to run the examples in the book.

If you look at this URL: Stimulus Reference
You will see that, to declare types and values, you need to use the declare keyword. Without it, when you try to access some property from elementToHideTarget it will raise an error, something like: can not access PROPERTY_NAME from undefined.

Might be a version mismatch, does it not work at all of you don’t use declare?

The TypeScript page was added July 14th per their GitHub repo, but I’m not sure it was tied to a change in how Stimulus works with those types, so I’m still curious as to the error you get if you don’t use declare.

for example, if I open css_controller.ts and remove the declare keyword in front of elementToChangeTarget: HTMLElement, I will get the following error:

Uncaught (in promise) TypeError: Stimulus Value "css.statusValue" - Cannot read properties of undefined (reading 'classList')

And the hide/show button stops working

How hard is it for you to swap out to stimulus 3.0.1? (Which is what my code uses), does it still fail?

(Trying to see if it’s just a version thing)

Noel

My code still works if I update stimulus to 3.1, and TypeScript to 4.8.whatever, so there must be some other setup difference between your app and my app.

@noelrappin my current package.json:

{
  "name": "app",
  "private": "true",
  "dependencies": {
    "@babel/preset-react": "^7.18.6",
    "@hotwired/stimulus": "^3.1.0",
    "@hotwired/turbo-rails": "^7.1.3",
    "@types/react": "^18.0.19",
    "@types/react-dom": "^18.0.6",
    "autoprefixer": "^10.4.8",
    "esbuild": "^0.15.7",
    "postcss": "^8.4.16",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "tailwindcss": "^3.1.8"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^5.36.2",
    "@typescript-eslint/parser": "^5.36.2",
    "cypress": "^10.7.0",
    "eslint": "^8.23.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-cypress": "^2.12.1",
    "eslint-plugin-prettier": "^4.2.1",
    "eslint-plugin-react": "^7.31.8",
    "prettier": "^2.7.1",
    "tsc-watch": "^5.0.3",
    "typescript": "^4.8.3"
  },
  "scripts": {
    "build:js": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets",
    "build:css": "tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css --minify",
    "failure:js": "rm ./app/assets/builds/application.js && rm ./app/assets/builds/application.js.map",
    "dev": "tsc-watch --noClear -p tsconfig.json --onSuccess \"yarn build:js\" --onFailure \"yarn failure:js\"",
    "lint": "yarn run eslint --ext .js --ext .jsx --ext .ts --ext .tsx app/javascript && tsc -p ./ --noEmit"
  }
}

Other thing I did different is the tsconfig.json file:

{
  "compilerOptions": {
    "declaration": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": ["es2022", "dom"],
    "jsx": "react",
    "module": "es6",
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "*": ["node_modules/*", "app/packs/*"]
    },
    "sourceMap": true,
    "target": "es2022",
    "noEmit": true
  },
  "exclude": ["**/*.spec.ts", "node_modules", "vendor", "public"],
  "compileOnSave": false
}

Tomorrow I will try with the same version you provided.