A Semantic Pattern for Simplifying Functional Components

Harnessing the power of hoisting (no longer just an interview question), you can rearrange a functional React component however you want by cutting it into smaller sections that flow from top to bottom. So for instance you can describe the overall structure of your component at the top of your component. When you first see it, it becomes a table of contents:

const CheatDay = ({
  items,
  healthWarnings,
  iAmSad,
  highFructoseCornSyrup,
  }) => {

  const CheatDayComponent = () => (
    <Container className="bag paper-bag brown-paper-bag bag-xl">
      <CheatDayHeader />
      <PunishingMeal />
      <CheatDayFooter />
    </Container>
  );

  ...

Progressing downward, you then define the subcomponents from top to bottom:

const CheatDayHeader = () => (
  <div className="header super-stylish-header">
    <h1>Cheat Day is like, super healthy and stuff</h1>
    <span>Enjoy your { highFructoseCornSyrup }</span>
  </div>
);

...

During the process, all of your props, etc. stay in scope because each subcomponent is a simple arrow function. When you’re cutting the component into subcomponents like this, you can then encapsulate specific logic to bring it closer to where it’s actually used, like this:

const PunishingMeal = () => {
  const overIndulge =
    iAmSad &&
    items.mcDonalds.bigMac || items.mcDonalds.nuggets &&
    healthWarnings.splice(0, 2).find('unheeded');

  return (
    <div className="classes more-classes flex">
      <h1 className="heading-six for-some-reason">
        {
          guiltyPleasures
        }
      </h1>
    </div>
  );
};

Right here it becomes glaringly obvious that whether to overIndulge is not a factor in my <PunishingMeal /> logic.

Then, dealing with funky conditional logic, you can swing output on a let variable:

let output;

consume(highFructoseCornSyrup);
iAmSad ? ignore(healthWarnings[0]) : ignore(healthWarnings[2]);

if (iAmSad) {
  output = (
    <Container>
      <Input type={ items.mcDonalds }>
        ...
      </Input>
    </Container>
  );
}

if (!iAmSad && items.häagenDazs) {
  output = (
    <Container>
      <Input type={ items.häagenDazs }>
        ...
      </Input>
    </Container>
  );
};

return output || null;

Each if statement above can now read similar to natural language: If I am sad, output equals .... Here we can easily tell that my love of ice cream transcends whether I’m sad. If it’s around I’ll eat it.

Otherwise, the return statement again reads like natural language: return output or null. This pattern makes it safe to remove conditional logic from JSX because it will simply do nothing if the return is null.

Next, by a similar pattern you can assign a semantic name to default content and change it on conditional logic:

let footerContent = (
  <div
    className="default-excuse"
    position="bottom">
      <p>Treat yo-self! You deserve this for being you.</p>
  </div>
);

if (!iAmSad) {
  footerContent = (
    <div
      className="maybe-get-chopt"
      position="bottom">
        <ul>
          { healthWarnings.map(warning => (<li>{ warning }</li>)) }
        </ul>
    </div>
  );
};

In this case, I’m more likely to pay attention to health warnings and consider getting Chop’t when I’m not sad. By default though, I’m going to give myself a flimsy excuse for overindulging when it’s time for a <CheatDay />.

Wrapping it up, I can then return some very straightforward JSX that’s easy to read:

return (
  <div className="footer footerClasses">
    { footerContent }
  </div>
);

Finally when I’m done, I can simply return the top-level component as if it were any JS function:

return <CheatDayComponent />;

At first the simplicity of the return statement could be a little jarring because it’s unusual to see JSX outside of parens. The point here again is semantics- it’s literally a natural language statement for what’s happening here.

And Here’s the Whole Shebang

import React from 'react';
import PropTypes from 'prop-types';
import { Container, Input } from '../Base';

const CheatDay = ({
  items,
  healthWarnings,
  iAmSad,
  highFructoseCornSyrup,
  }) => {

  const CheatDayComponent = () => (
    <Container className="bag paper-bag brown-paper-bag bag-xl">
      <CheatDayHeader />
      <PunishingMeal />
      <CheatDayFooter />
    </Container>
  );

  const CheatDayHeader = () => (
    <div className="header super-stylish-header">
      <h1>Cheat Day is like, super healthy and stuff</h1>
      <span>Enjoy your { highFructoseCornSyrup }</span>
    </div>
  );

  const PunishingMeal = () => {
    const overIndulge =
      iAmSad &&
      items.mcDonalds.bigMac || items.mcDonalds.nuggets &&
      healthWarnings.splice(0, 2).find('unheeded');

    return (
      <div className="classes more-classes flex">
        <h1 className="heading-six for-some-reason">
          {
            guiltyPleasures
          }
        </h1>
      </div>
    );
  };

  const guiltyPleasures = items.map(item => {
    const foodItems = Object.keys(item);
    const ingredients = foodItems.map(foodItem => foodItems[foodItem]);

    const ignoreFullness = () => {
      let output;

      consume(highFructoseCornSyrup);
      iAmSad ? ignore(healthWarnings[0]) : ignore(healthWarnings[2]);

      if (iAmSad) {
        output = (
          <Container>
            <Input type={ items.mcDonalds }>
              <ul>
                { items.mcDonalds.bigMac.map(ingredient => (
                  <li>
                    { ingredient }
                  </li>
                )) }
              </ul>
            </Input>
          </Container>
        );
      }

      if (!iAmSad && items.häagenDazs) {
        output = (
          <Container>
            <Input type={ items.häagenDazs }>
              <ul>
                { items.häagenDazs.iceCream.map(ingredient => (
                  <li>
                    { ingredient }
                  </li>
                )) }
              </ul>
            </Input>
          </Container>
        );
      };

      return output || null;
  });

  const CheatDayFooter = () => {
    let footerContent = (
      <div
        className="default-excuse"
        position="bottom">
          <p>Treat yo-self! You deserve this for being you.</p>
      </div>
    );

    if (!iAmSad) {
      footerContent = (
        <div
          className="maybe-get-chopt"
          position="bottom">
            <ul>
              { healthWarnings.map(warning => (<li>{ warning }</li>)) }
            </ul>
        </div>
      );
    };

    return (
      <div className="footer footerClasses">
        { footerContent }
      </div>
    );
  };

  return <CheatDayComponent />;
};

CheatDay.propTypes = {
  items: PropTypes.shape({
    mcDonalds: PropTypes.shape({
      bigMac: PropTypes.shape({
        allBeefPatties: PropTypes.array,
        specialSauce: PropTypes.mystery.isRequired,
        lettuce: PropTypes.vegetable,
        cheese: PropTypes.dairy,
        pickles: PropTypes.monster,
        onions: PropTypes.vegetable,
        sesameSeedBun: PropTypes.carbs,
      }),
      nuggets: PropTypes.shape({
        chickenProduct: PropTypes.mystery.isRequired,
        sauce: PropTypes.string.isRequired,
      }),
      fries: PropTypes.shape({
        potato: PropTypes.starch.isRequired,
        oil: PropTypes.oil.isRequired,
        salt: PropTypes.deliciousness,
      }).isRequired,
    }),
    häagenDazs: PropTypes.shape({
      iceCream: PropTypes.shape({
        ice: PropTypes.coldness.isRequired,
        cream: PropTypes.umami.isRequired,
      }).isRequired,
      toppings: PropTypes.shape({
        reeses: PropTypes.bestCandyEver,
        whippedCream: PropTypes.umami,
      })
    }),
  }),
  healthWarnings: PropTypes.array,
  iAmSad: PropTypes.boolean.isRequired,
  highFructoseCornSyrup: PropTypes.hazard.isRequired,
};

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax

Mark Bello

Mark Bello