7

Before proceeding, I should mention that yes, I have already read the questions and answers on "Use if statement in React JSX" and its different variants on SO and elsewhere.

However, these posts are more about how to get around without using statements in JSX. I'd like to know why statements aren't allowed in JSX, for which I cannot find any posts on.

I am reading the official documentation on this called "If-Else in JSX", and the reason given for why is, quote,

JSX is just syntactic sugar for function calls and object construction

They go on to contrast the following two pieces of code, the first of which works and the second one doesn't work:

This is valid:

// This JSX: 
ReactDOM.render(<div id="msg">Hello World!</div>, mountNode);
// Is transformed to this JS: 
ReactDOM.render(React.createElement("div", {id:"msg"}, "Hello World!"), mountNode); 

This is not valid:

// This JSX:
<div id={if (condition) { 'msg' }}>Hello World!</div>

// Is transformed to this JS:
React.createElement("div", {id: if (condition) { 'msg' }}, "Hello World!");

I would really like to understand this under the hood. First of all, in the second example, I would never have thought to write JavaScript inside the id property of an HTML element. In fact, this is the first time I've seen code of any sort used in an id property. If I were to try to write an if conditional, I would just do it in curly braces within the render return expression, as a naïve analog of other JS that works (like map or ternary expression).

render() {
return (
  {if ...
}
)

I have no doubt that it is perfectly clear to the author of this document that this slightly unorthodox example explains their assertion that "JSX is just syntactic sugar for function calls and object construction", but I cannot figure out how.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
thetrystero
  • 5,622
  • 5
  • 23
  • 34
  • 3
    The code you provide is the same in every letter. – keikai Apr 02 '20 at 09:48
  • `valid` and `not valid` are the same – Mark Minerov Apr 02 '20 at 09:48
  • The arguments to functions (or values of object literals, in your case) must be expressions. `({ id: if (condition) { 'msg' }})` is a syntax error. – jonrsharpe Apr 02 '20 at 09:58
  • Whoops sorry cut and paste the wrong one. But the link to the documentation page can see the exact examples. – thetrystero Apr 02 '20 at 10:01
  • Those examples show you why. If you have statements in JSX, it gets transformed to function calls and object literals *that have syntax errors*. – jonrsharpe Apr 02 '20 at 10:04
  • But why did they write JS inside the id attribute? Is that even allowed. So if I were to write a ternery inside the id attribute that would work? How does writing js inside the id attribute motivate this particular explanation ? – thetrystero Apr 02 '20 at 10:06
  • Or another way, one wouldn't normally write js in the attribute id though. You'd write it in the body of the return. If you do that how would this example look like and how does that explain the acceptance of expressions and not of statements? – thetrystero Apr 02 '20 at 10:09
  • Why *wouldn't* they? That's what JSX gives you, the ability to write HTML-like syntax with dynamic behaviour. You can do it with any attribute. It's not at all clear to me what you're trying to ask here. Your last snippet seems to be putting curly braces *outside* the JSX, which is not how that works. – jonrsharpe Apr 02 '20 at 10:09
  • Ah ok I think I see where you're going. They just showing one particular use case . So the second argument of createElement function has to be valid. Why is if statement not valid as argument? So createElement(..., state.Start ? ... : ...,...) would be valid? I don't see how this is more or less valid than using the if statement. – thetrystero Apr 02 '20 at 10:14
  • *All* the arguments have to be valid! It's more or less valid because *that's what the language specification says*. Also it doesn't really make sense to pass a statement as a value. – jonrsharpe Apr 02 '20 at 10:15
  • Yes corrected last snippet. Thanks – thetrystero Apr 02 '20 at 10:18
  • Yes it's what the spec says I'm trying to understand how under the hood. Can you explain why the ternery is valid as second argument of create Element but if statement is not valid as second argument of createElement exactly? – thetrystero Apr 02 '20 at 10:19
  • And please don't answer "because second argument only takes expression not statement" – thetrystero Apr 02 '20 at 10:20
  • I don't know what else you think I could say. That *is* why. An `if` statement doesn't have a value to *be* the argument, a ternary expression does. Forget JSX, `myFunction(if (true) { "hello world" })` is not valid JS syntax. – jonrsharpe Apr 02 '20 at 10:22

2 Answers2

4

Let's start conceptually. What are the definitions of statement vs expression?

A statement does something. An expression evaluates to a value.

JSX is meant to be built up and passed around from one segment of your code to another, eventually ending up as HTML. The name even suggests this "JavaScript to XML" conversion.

The whole point of it is to return a "value" of HTML nodes. JSX kindly allows for expressions, because those help you determine values.

Perhaps it will help to take a closer look at the difference between a ternary expression and an if/else.

If/Else

if(isSaturday){
   wakeUpHour = 10;
}else{
   wakeUpHour = 7;
}

Ternary

wakeUpHour = isSaturday ? 10 : 7;

Those both accomplish the same thing, right? But under the hood they are operating differently. In English, the if/else might read:

  1. If the value of 'isSaturday' is truthy, run the code inside the curly braces
  2. Assign the number 10 to 'wakeUpHour'
  3. Otherwise, run the code inside the next curly brace
  4. Assign the number 7 to to 'wakeUpHour'

The ternary statement also has two parts:

  1. If isSaturday is truthy, have a value of 10. Otherwise have a value 7.
  2. Assign this value to 'wakeUpHour'

We think of those as accomplishing the same thing. The key point here is that the ternary expression itself is just a value. It's not lines of codes. To do something with that value required another part, assigning it.

In JSX, we don't want to be assigning things. We want values. So we are just taking the ternary expression (a value), not the assignment part or any other code statements.

Finally, and hopefully not to add to your confusion, I would note that you can define functions in JSX.

const myJSX = <button onClick={ () => { return 'hello'; } }>Say hello</button>

Wait, what? I thought we couldn't execute lines of code. It's not executing the lines of code, it's defining them; it's rendered to:

var myJSX = React.createElement("button", {onClick: () => {
  return 'hello';
}}, "Say hello");

Compare that with trying to just throw in an if/else statement:

const myJSX = <span>{ if(true){ return 'hello'; } }</span>

Which would try to render as:

var myJSX = React.createElement("span", null, if(true){ return 'hello' });

That doesn't work, for the same reason that you can't normally pass an unencapsulated chunk of code into an argument of a function.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
tmdesigned
  • 2,098
  • 2
  • 12
  • 20
  • That's not a function *call*, it's a function *definition* - maybe use an example where that's not a child, because functions aren't valid children. Also I don't think the Babel JSX plugin alone will switch from an arrow function to a vanilla function. – jonrsharpe Apr 02 '20 at 10:29
  • True, good catch. I do believe babel converts arrow functions by default, but I could be wrong. – tmdesigned Apr 02 '20 at 10:32
  • https://babeljs.io/docs/en/babel-plugin-transform-react-jsx doesn't, I don't think, likely others in the chain (e.g. preset-env) will. – jonrsharpe Apr 02 '20 at 10:34
  • You are correct, I was running it through es2015 as well. – tmdesigned Apr 02 '20 at 10:35
  • Great thank you! Your motivation of JSX helping produce HTML nodes is really great visualization! – thetrystero Apr 02 '20 at 10:40
2

Maybe this is irrelevant to your question, but I've been redirected here because one of my questions said to be duplicate to your question. if you want to do multi-line JavaScript code, you can wrap your JS code with an IIFE, for example:

  <b>
    {(() => {
      const a = [1, 2, 3].find((el) => el === 2)
      // as much code as you want ...
      // ...
      // ...
      console.log(a)
    })()}
  </b>
Ekmek
  • 413
  • 2
  • 12