wrahim
wrahim

Reputation: 1168

What's the difference between functions that render JSX vs. declaring components inside another component?

Is this an anti-pattern?

export function Todo() {
    ...

    const renderItem = (item) => (
        item.done
            ? <strike>{item.text}</strike>
            : <span>{item.text}</span>
    );

    return (
        <ul>
            {items.map((item) => <li>renderItems(item)</li>)}
        </ul>
    );
}

What's the difference between rendering items like that, as compared to making an Item component inside Todo, such as this:

export function Todo() {
    ...

    const Item = (props) => (
        props.item.done
            ? <strike>{item.text}</strike>
            : <span>{item.text}</span>
    );

    return (
        <ul>
            {items.map((item) => <li><Item item={item} /></li>)}
        </ul>
    );
}

EDIT:

What about creating components/render functions locally, that are called once?

export function SomeForm(props) {
    const renderButton = (isComplete) => (
        isComplete
            ? <button>Submit</button>
            : <button disabled>Please complete</button>
    );

    return (
        <form>
            <input />
            {renderButton(props.isDone)}
        </form>
    );
}

Upvotes: 6

Views: 3205

Answers (1)

Dennis Vash
Dennis Vash

Reputation: 53874

Beforehand lets fix the examples into a valid code:

// #1
export function Todo({items}) {
  const renderItem = (item) =>
    item.done ? <strike>{item.text}</strike> : <span>{item.text}</span>;

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{renderItems(item)}</li>
      ))}
    </ul>
  );
}

// #2
export function Todo({items}) {
  const Item = (props) =>
    props.item.done ? <strike>{item.text}</strike> : <span>{item.text}</span>;

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          <Item item={item} />
        </li>
      ))}
    </ul>
  );
}

Back to the question, Yes those are anti-patterns.

Why it's an anti-pattern?

In both examples, you will rerender the item even though there wasn't any visual change.

The reason for it because on every render, you recreate both function (renderItem) and function component (Item).

What you want

Instead, you want to let React do the Reconciliation process, for it you need render a static tree as much as possible.

The easiest solution is just move the function/function component, to the outer scope or inlining the logic into the tree itself.

const renderItem = (item) => (...)
const Item = (props) => (...)

export function Todo({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          (item.done ? <strike>{item.text}</strike>:<span>{item.text}</span>)
        </li>
      ))}
    </ul>
  );
}

What's the difference between rendering items like that

renderItem is just a function returning JSX, Item is a React component, therefore its state is registered to the React tree ("just a function" can't hold its own state).

Upvotes: 14

Related Questions