0

I'm trying to understand how this code works, because it seems like it should not, but it does work.

Is this some TypeScript magic I'm not understanding? React magic? How does this work if the cases are not strings, but imported values which appear as undefined?

Code source
import DialogTitle, { DialogTitleProps } from "./Title";
import DialogDescription, { DialogDescriptionProps } from "./Description";
import DialogButton, { DialogButtonProps } from "./Button";

// ....

  React.Children.forEach(children, (child) => {
    if (typeof child === "object" && child !== null && "type" in child) {
      switch (child.type) {
        case DialogTitle: // DialogTitle is undefined in the debugger when hovered
          titleChildrens.push(child as TitleElement);
          return;
        case DialogDescription: // DialogDescription is undefined in the debugger when hovered
          descriptionChildrens.push(child as DescriptionElement);
          return;
        case DialogButton: // DialogButton is undefined in the debugger when hovered
          if (Platform.OS === "ios" && buttonChildrens.length > 0) {
            buttonChildrens.push(
              <View
                style={[
                  verticalButtons
                    ? styles.buttonSeparatorVertical
                    : styles.buttonSeparatorHorizontal,
                  buttonSeparatorStyle,
                ]}
              />
            );
          }
          buttonChildrens.push(child as ButtonElement);
          return;
      }
    }
    otherChildrens.push(child);
  });

enter image description here

enter image description here

Slbox
  • 10,957
  • 15
  • 54
  • 106
  • You misunderstand. The code is functional and not mine. I need to understand _WHY_ it functions, because it sure looks like it shouldn't - but it does. – Slbox May 11 '22 at 22:30
  • I think the debugger is misleading you. Try adding in a `console.log(DialogTitle`) statement, and i suspect you will get a value that is not `undefined`. – Nicholas Tower May 11 '22 at 22:49
  • Unfortunately not. `ReferenceError`. I've added the image to the question. You can see there that the `case` is indeed working, despite that. – Slbox May 11 '22 at 22:54
  • 1
    I mean to put the console.log statement into the *code*. It will not work in the console. When you run `console.log(DialogTitle)` in the console, it is trying to refer to a global variable named `DialogTitle`, which does not exist. – Nicholas Tower May 11 '22 at 22:56
  • Derp. Trying that. – Slbox May 11 '22 at 22:59
  • Indeed the `console.log(DialogTitle)` outputs `ƒ`. But still, how does this switch statement work? – Slbox May 11 '22 at 23:16
  • 1
    It checks if the type `===` the function `DialogTitle` (which was imported at the top of the file), and if so it goes into the first case. If not, it checks if the type `===` the function `DialogDescription`, and enters that case. And so on. None of the values are `undefined`, they're each independent functions, so this check works fine and identifies which type of component was received (among a limited set) – Nicholas Tower May 11 '22 at 23:20

1 Answers1

2

The code you are seeing in the debugger is not the code being executed. What you see as Container.tsx is actually probably just a snippet of compiled pure JS in a huge bundle.js somewhere.

Typescript code requires compilation before the browser can read it. As part of this step, it also generates source maps which map the compiled code back to your original source files. Webpack or whatever bundler you're using probably does it's own compilation then, and makes new source maps.

For the most part this makes it easier to debug, but it isn't perfect. So what you think is DialogTitle probably is an identifier that got transformed somehow during compilation, minification, obfuscation, bundling, etc...

You can disable source maps entirely to get a clearer view if what's going on, though the code your debugging will probably start to look pretty cryptic.

Source maps usually do work just fine, but not all tools can generate them perfectly, I've observed.


As for how the switch statement works, functions have an identity which can be queried by a switch statement.

For example, this works because MyComponent is assigned either A or B and each case can test that.

function A() { return <>A</> }
function B() { return <>B</> }

const MyComponent = Math.random() > 0.5 ? A : B

switch(MyComponent) {
  case A: console.log('got A'); break
  case B: console.log('got B'); break
}
// logs "got A" or "got B"

See playground


In the code I referenced, it's switch(child.type) and I don't understand how child.type is compared against a function? child.type is itself a function, but I'm still very confused about how that comparison evaluates to true.

This delves into react internals a bit, so I can't vouch for why but this appears to be what's going on.

When a component is rendered it returns a JSX.Element object. This object appears to have a type property on it that is a reference to the component that rendered that JSX.

For example:

function A() { return <>A</> }
console.log(<A />);

Yields this in the console:

jsx result in console

This means that rendered JSX remembers the component that rendered it via that type property. And this loop here checks to see if any children were rendered with specific components that may need special handling.

Here's a minimal example that's closer to what your original snippet is actually doing.

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • You're right, it's apparently a source mapping issue - but can you help point me towards how this kind of switch statement can work with a `function` as the case value and help me get to a full answer for this? – Slbox May 11 '22 at 23:20
  • 1
    Functions have an identity that a switch statement can test. See my update. – Alex Wayne May 11 '22 at 23:26
  • Thank you very much for your edit! That all makes great sense, but I'm still stuck on a single point. | In the code I referenced, it's `switch(child.type)` and I don't understand how `child.type` is compared against a `function`? `child.type` is itself a function, but I'm still very confused about how that comparison evaluates to `true`. – Slbox May 11 '22 at 23:30
  • That's based on how react renders and serializes JSX. It's just looking for JSX generated from components that need extra handling. See my update. – Alex Wayne May 12 '22 at 00:07