Declarative Gibberish in React
Tags: react, declarative, refactoring, javascript
Published: 2020-05-02 20:46:22

I've written before about React's slavish devotion to declarative syntax (full article here: https://dev.to/bytebodger/react-s-odd-obsession-with-declarative-syntax-4k8h).  So I'd like to give a real-life example from a very popular NPM package: `react-table`.

In their "Quick Start" guide, they show an example of how to build a table with their package.  Bear in mind that this is just the "Quick Start" example - so what we're seeing is, presumably, one of the most basic use-cases for the package.  This is their example:

```javascript
  return (
    <table {...getTableProps()} style={{ border: 'solid 1px blue' }}>
      <thead>
        {headerGroups.map(headerGroup => (
          <tr {...headerGroup.getHeaderGroupProps()}>
            {headerGroup.headers.map(column => (
              <th
                {...column.getHeaderProps()}
                style={{
                  borderBottom: 'solid 3px red',
                  background: 'aliceblue',
                  color: 'black',
                  fontWeight: 'bold',
                }}
              >
                {column.render('Header')}
              </th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody {...getTableBodyProps()}>
        {rows.map(row => {
          prepareRow(row)
          return (
            <tr {...row.getRowProps()}>
              {row.cells.map(cell => {
                return (
                  <td
                    {...cell.getCellProps()}
                    style={{
                      padding: '10px',
                      border: 'solid 1px gray',
                      background: 'papayawhip',
                    }}
                  >
                    {cell.render('Cell')}
                  </td>
                )
              })}
            </tr>
          )
        })}
      </tbody>
    </table>
  )
```

_Yikes..._

I mean... I can certainly _understand_ what's happening in this example.  And if you're a React dev, I hope that you can, too.  But for such a small bit of functionality, there sure is _a lot_ going on here.  And it's not necessarily simple to just trace the flow.
<br/>

## Differing Standards of Readability

First, let me make it clear that I'm in no way attacking `react-table`.  I could have chosen a thousand different examples from all over the web.  This is just one that happened to jump out at me.  

Second, I imagine that many of you are looking at the example above and thinking:

> Yeah... so?  What's wrong with it??

I've noticed that React devs, in general, seem to have naturally coalesced around this type of style.  I see examples, like the one above, almost everywhere I look in React.  And anytime we venture into the realm of coding _styles_, we're obviously veering hard into subjective, personal choices.

But even with all those caveats in mind, I still can't hide the fact that **I _hate_ this style of JSX**.  Personally, I find it very hard to read.  There are **_four_** separate loops happening in the middle of our layout.  And if it's hard to read, that means that it could also be hard to follow the logic and spot any inherent flaws.  Personally, I never want to see a whole heap of _logic_ stuffed into the middle of my JSX.
<br/>

## An Alternate Approach

Rather than argue about what is putatively good-or-bad in the above example, I think it's more useful to illustrate how this would look _in my code_:

```javascript
const getCells = (cells) => {
  return cells.map(cell => (
    <td
      {...cell.getCellProps()}
      style={{
        padding: '10px',
        border: 'solid 1px gray',
        background: 'papayawhip',
      }}
    >
      {cell.render('Cell')}
    </td>
  ));
};

const getHeaderGroups = () => {
  return headerGroups.map(headerGroup => (
    <tr {...headerGroup.getHeaderGroupProps()}>
      {getHeaders(headerGroup.headers)}
    </tr>
  ));
};

const getHeaders = (headers) => {
  return headers.map(header => (
    <th
      {...header.getHeaderProps()}
      style={{
        borderBottom: 'solid 3px red',
        background: 'aliceblue',
        color: 'black',
        fontWeight: 'bold',
      }}
    >
      {header.render('Header')}
    </th>
  ));
};

const getRows = () => {
  return rows.map(row => {
    prepareRow(row);
    return (
      <tr {...row.getRowProps()}>
        {getCells(row.cells)}
      </tr>
    );
  });
};

return (
  <table {...getTableProps()} style={{ border: 'solid 1px blue' }}>
    <thead>
      {getHeaderGroups()}
    </thead>
    <tbody {...getTableBodyProps()}>
      {getRows()}
    </tbody>
  </table>
)
```

Is my approach a _wee bit_ longer?  Yep.  It sure is.  But if your only standard of code quality is _brevity_, then I'm sure you're accustomed to writing some craptastic, cryptic code.

_Buy why did I choose to break up the code the way that I did?_
<br/>

## Declarative... When It Suits You

React devs talk about _declarative code_ similarly to how other people talk about their first car.  They get all starry-eyed.  And... they get _theoretical_.  They start spewing all the bits of code theory that they can pull out of their head to tell you why declarative is just soooooo much better than imperative.

Then they crank out a block of code like that initial example.  And for some reason, this never seems to strike them as contradictory.  But the first example is chuck full of _imperative_ logic.

You see, for too many React devs, their definition of "declarative" just means: "It happens _inside_ the `return` statement."  For those same devs, their definition of "imperative" is: "It happens _outside_ the `return` statement."

I've even seen (misguided) articles about "React Best Practices" claiming that: "You shouldn't have _any_ JSX outside the `return` statement."  But that's just ignorant.

In the first example given above, we basically have 3 options for how to deal with our table logic:

1. Cram the header-aggregation, row-aggregation, and cell-aggregation _into_ the `return` statement.  (Which is what the original author did.)  This isn't technically _wrong_ - but it's a far cry from being _declarative_.<br/>

2. Move the loops, conditionals, and other logic into their own standalone functions.  (Which is what I did in the second example.)  I would actually argue that my approach is _more compliant_ with declarative practices than the first example.<br/>

3. Move the loops, conditionals, and other logic into completely separate standalone _components_.  This is technically _feasible_ - but it can lead to an insane explosion of single-use helper components.<br/>

I chose option #2 because I don't want any conditionals or loops _inside_ my JSX.  When I'm looking at a block of JSX, I want that block to represent, as much as possible, pure _layout_.  Every time I start chucking loops and control statements _into_ my JSX, the JSX gets farther away from being _declarative_ layout, and closer to being a thinly-disguised _imperative_ function.

> Whenever you start reaching for loops and conditional statements _inside_ of your `return` statement, it's an indication that this _imperative_ logic may be better suited to its own helper function.
<br/>

## The Irony of Declarative Acolytes

I'm aware of a certain, bizarre irony anytime I'm engaged with someone who believes themselves to be a hardcore fan of declarative syntax.  On one hand, they seem to despise any logic that can't be naturally represented with a JSX tag (which is, under the covers, just _a function call_).  On the other hand, they will happily crank out `return` statements that are stuffed to the gills with _imperative code_.  

Maybe you think I'm being silly, but I swear that many of React's declarative fanboys (yeah - there's _that word_ again...) would be perfectly fine with this:

```javascript
export default function DeclarativeUserInfo() {
  return (
    <>
      {[1].map((render, index) => {
        if (someCondition)
          return null; 

        const getNames = () => {
          // all the get-names logic here
          return [firstName, lastName, middleInitial];
        };

        const getStreetAddress = () => {
          // all the get-address logic here
          return [streetAddress1, streetAddress2];
        };

        const getCity = () => {
          // all the get-city logic here
          return city;
        };

        const getStateOrProvince = () => {
          // all the get-state/province logic here
          return stateOrProvince;
        };

        const getCountry = () => {
          // all the get-country logic here
          return country;
        };

        const getPostalCode= () => {
          // all the get-postal-code logic here
          return postalCode;
        };

        const [firstName, lastName, middleInitial] = getNames();
        if (!lastName) 
          return <div>I like sammiches.</div>;
        const [streetAddress1, streetAddress2] = getStreetAddress();
        const city = getCity();
        const stateOrProvince = getStateOrProvince();
        const country = getCountry();
        const postalCode = getPostalCode();
        return (
          <div key={'imperative-mess-' + index}>
            <div>
              {firstName} {middleInitial}. {lastName}
            </div>
            <div>{streetAddress1}</div>
            <div>{streetAddress1}</div>
            <div>
              {city}, {stateOrProvince}  {postalCode}
            </div>
            <div>{country}</div>
          </div>
        );
      })}
    </>
  );
}
```

I'm not even trying to be funny here.  Show this example to some of your React friends and ask them if it's _imperative_ or _declarative_?  You might be surprised by some of the answers that you get.  Cuz there are more-than-a-few of my React colleagues out there who'd swear that this is _declarative_ code.

Granted, it is, _sorta_, "declarative" from the perspective that we're _declaring_ that, whatever is produced by the (silly) `map()` function will, in turn, be rendered at the place where the function is invoked.  But that's a pretty-weak standard for "declarative".
<br/>

## Room For Nuance

If I sound like I'm extremely pedantic about my personal imperative-vs-declarative rules, believe me, I'm not.  There's more-than-enough room in good React code for declarative _and_ imperative constructs - even _inside_ JSX.  

Let me give you one small example of where I use imperative techniques _inside_ JSX all the time:

```javascript
export default function Foo() {
  return (
    <>
      <div style={{display : global.isLoggedIn ? 'block' : 'none'}}>
        Here is all the stuff we show to logged-in users.
      </div>
      <div style={{display : global.isLoggedIn ? 'none' : 'block'}}>
        Here is what we show to non-logged-in users.
      </div>
    </>
  );
}
```

IMHO, this approach is just sooooooo _elegant_.  No need to spin up helper functions.  And I greatly prefer this to the more-common approach like this:

```javascript
export default function Foo() {
  return (
    <>
      {global.isLoggedIn && 
        <div>
          Here is all the stuff we show to logged-in users.
        </div>
      }
      {!global.isLoggedIn &&
        <div>
          Here is what we show to non-logged-in users.
        </div>
      }
    </>
  );
}
```

In fact, CSS in general represents an excellent use case for inline ternary operators.  It's not limited to `display`.  You can efficiently switch an element's `color`, or `width`, or... _anything_ by just setting the CSS value with a ternary operator.

Can I give you any _empirical_ reason why my approach is "better"??  Umm... no.  I admit, it's largely a question of coding _style_.  But for whatever reason, my approach feels much more _declarative_ to me.  While the second approach feels more like _imperative_ code that's been stuffed in a faux-declarative wrapper.
<br/>

## Conclusions

I'm not one of those React devs who thinks that every bit of logic should be shoehorned into a declarative JSX tag.  I also fail to see how wrapping a block of imperative logic with `{  }` inside your JSX magically makes it declarative.  

One of the allures of a declarative style is its inherent _readability_.  The more imperative code you try to cram into a supposedly-declarative container, the more you sacrifice that readability.  

And there's a certain contradiction I've often scene in the Functional Programming Drones.  They preach endlessly about the ability to break features down into smaller-and-smaller functions.  But then, when it comes time to crank out some layout inside React's JSX, it's almost like they're afraid (or can't be bothered) to follow their own dictates and split things off into those separate functions.  Instead, they just cram all the _imperative_ logic right into the middle of the JSX.