2

Internet. Long-time listener, blah blah blah.

I've got a React app using Typescript. I've got a function in one file that accepts a limited range of strings, and a function in a different file that calls that thing. I was ready to move on this morning when I realized that - duh - this is probably a perfect opportunity to do some type-checking, improving stability going forward.

Since then, I've been slowly losing my mind. There's something important I'm missing, but no amount of reading or randomly combining types and interfaces has gotten me any closer. Any help is appreciated.

ForestPage:

toggle = (e: any) => {
  const target = e.currentTarget.dataset.toggletarget;
  Tree.toggle(target);
}

Tree:

type cart = "elm" | "oak" | "pine";
type Itoggle = (target: cart) => void;

const toggle: Itoggle = (target: cart) => {
  console.log('Now toggling', target);
}

The catch here is that ForestPage can pass any string whatsoever, and everything will proceed just fine. What is "cart" doing, if not establishing a type?

I started putting this into Code Snippet, but it didn't seem to have the proper libraries (including TypeScript), and this post is already two pages and it's 4:00 on a Friday.


The code above is the simplest version of my code. I've also tried a variety of other options.

https://www.tutorialsteacher.com/typescript/typescript-interface

type cart = "elm" | "oak" | "pine";
interface Itoggle = {
  (target: cart) : void
}

const realToggle = (target: cart) => {
  console.log(target);
}

const toggle: Itoggle = realToggle;

First, tsLint changed Itoggle from an interface back to a type, on its own. Then, the results were unchanged - I can still pass in any value to target.


How to require a specific string in TypeScript interface

This where I learned that 'type cart' could replace 'enum cart'.


https://medium.com/@KevinBGreene/typescript-enums-and-polymorphism-with-type-matching-fc3dc74b031c

enum cart = elm, oak, pine;

interface elm {
  target: cart.elm
}

interface oak {
  target: cart.oak
}

type targetType = elm | oak

const toggle = (target: targetType) => {
  console.log(target)
}

I think I had this working, but I needed to have

console.log(target.target.target)

which is obviously stupid.

const toggle = (target: cart) => {

errored out. I'm afraid I don't remember exactly how, but think it was something to do with the fact that enums are innately of type {[key: string]: number}, and I was just sending a string.


https://2ality.com/2020/01/typescript-enums.html#use-case%3A-safer-string-constants

Changed the enum from before to:

enum cart {
  elm: "elm",
  oak: "oak",
  pine: "pine"
}

but it still wanted to be fed some kind of object, and it was looking increasingly unlikely, anyway.


https://thoughtbot.com/blog/booleans-and-enums

Useful article about enums, but it said nothing about parameters. I abandon looking at enums, pretty sure I'm barking up the wrong tree. Back to types.


But, but... they should work, right? https://www.typescriptlang.org/docs/handbook/enums.html


I also tried experimenting with generating new types and enums on the fly, based on available data, but that made the linter break down. Which, I guess, makes sense. It's not built at the point that it needs to be read.

interface pleaseWork {};

const options = ["elm", "oak", "pine"];
_.each(options, (option) => {
  pleaseWork[option] = option...  etc, etc

Various varieties therein, of course, none of which were any good. Again, logically, that makes some sense. But I was cracking by this point.


At any rate, any guidance is appreciated. I've looked at it long enough that nothing makes sense anymore, and I don't feel like I'll make progress without at least a gentle shove in the right direction.

  • Cart is defining the allowed string values as can be seen here [Playground Link](http://www.staging-typescript.org/play?#code/C4TwDgpgBAxghgJ2FAvFARBANgW3VAHwwHs4BrfI9MASwDsJ0BuAKFEigElhiBzXrNDQAKYIl4RgALliJgASlQA+KADdiNACasWMYnQDOyHv0EzufAUKijxkmfCSKUKgN4sosfQeKCAdFh8wgDkAHLEAO5QJgL0vMEANNF2CqwAvixsloLC6ABGWHAAFgCE6PJZphC52HjyQA). See how the transpiler complains about "Blah!" but not "elm". Are you trying to get your code to throw an error during runtime if the cart value is not one of the allowed values? – Ben Smith May 22 '20 at 22:30

1 Answers1

0

I think you're focusing on fixing the wrong thing. Your first version of Tree should be fine. But the spot where you're calling it, you're calling it with an any. e is an any, and thus any properties on e are also any. That effectively tells typescript not to check your types.

toggle = (e: any) => {
  const target = e.currentTarget.dataset.toggletarget;
  Tree.toggle(target);
}

I don't know exactly what type of event this is so i'm not sure the right type, but as an example:

toggle = (e: React.MouseEvent<HTMLButtonElement>) => {
  const target = e.currentTarget.dataset.toggletarget;
  Tree.toggle(target);
}

With this code, typescript will see that target is a string and thus prevent you from calling Tree.toggle, since that's too broad of a type. Add in some code to check what string it is, and it will be allowed

toggle = (e: React.MouseEvent<HTMLButtonElement>) => {
  const target = e.currentTarget.dataset.toggletarget;
  if (target === 'elm') {
    Tree.toggle(target);
  }
}
Nicholas Tower
  • 72,740
  • 7
  • 86
  • 98
  • Oh, _man_. That absolutely could be it - I would have expected the permissions to get increasingly stringent, but "any" overriding "everything" sounds like exactly the kind of nonsense that I'm always wrong about. By increasingly 'stringent' I mean, for example, accepting "anything" at the top level. But then the next function requires it to match type "vehicle," then the function that calls insists upon "truck." Or, you know, whatever. As opposed to "anything" cascading down the line. I'll give that a try the next chance I get and report back. Thanks so much. – Chris Young May 22 '20 at 23:47