Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix conditionals in JSX #2200

Merged
merged 2 commits into from
Mar 2, 2020
Merged

Fix conditionals in JSX #2200

merged 2 commits into from
Mar 2, 2020

Conversation

jimbo
Copy link
Contributor

@jimbo jimbo commented Feb 27, 2020

Description

In this codebase, we avoid multiline statements in JSX because they defeat the main purpose of using JSX: readable trees. In practice, this only comes up when conditionally rendering JSX elements. We've established a pattern of first defining conditional elements as variables (constants), then inserting those variables into the final tree, and we've been pretty good about using this pattern. But we haven't been perfect, and that makes the pattern unclear.

This PR corrects all known cases in which we were not adhering to this pattern.

// ❌
return (
  <div>
    {condition ? (
        <Foo />
      ) : (
        <Bar />
      )
    }
  </div>
)

// ✅
const element = condition ? (
  <Foo />
) : (
  <Bar />
)

return (
  <div>
    {element}
  </div>
)

Related Issue

PWA-415

Acceptance

Verification Steps

  1. yarn test

Screenshots / Screen Captures (if appropriate)

Checklist

  • I have updated the documentation accordingly, if necessary.
  • I have added tests to cover my changes, if necessary.

@PWAStudioBot
Copy link
Contributor

PWAStudioBot commented Feb 27, 2020

Messages
📖

Access a deployed version of this PR here. Make sure to wait for the "pwa-pull-request-deploy" job to complete.

📖 DangerCI Failures related to missing labels/description/linked issues/etc will persist until the next push or next nightly build run (assuming they are fixed).
📖

Associated JIRA tickets: PWA-415.

Generated by 🚫 dangerJS against c04444b

@@ -39,14 +39,14 @@ export default props => {
const cards = getGiftCards(props.data);

return cards.value ? (
<>
<Fragment>
Copy link
Contributor

@sirugh sirugh Feb 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this preferred over <>? Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's a bit more explicit and portable. Generally, you can paste our code into a sandbox and it'll work. This particular transform isn't as popular as you might think.

Also, we've used Fragment almost everywhere else.

@m2-community-project m2-community-project bot moved this from Ready for Review to Review in Progress in Pull Request Progress Feb 27, 2020
@@ -24,43 +24,46 @@ const ShippingMethods = props => {

const classes = mergeClasses(defaultClasses, props.classes);

const radios =
isShowingForm && hasMethods ? (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using useMemo here? @sirugh and I had a conversation yesterday about this. He can expand more on the useMemo use case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to draw the line somewhere on memoization, and in this codebase, I've generally drawn that line at element creation. The only place we should be memoizing element creation (afaik) is when we're doing it in a loop.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when we're doing it in a loop

Like...a render loop? 😆

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But seriously, imagine a toggleable element that we expect a user to interact with quite often. Shouldn't we memoize the state to avoid re-creating the component unnecessarily?

This is the difference we're talking about, I think. Which of these is "more performant"/"better"?

const MyComponent = ({ renderChildren }) => {
    const FooComponent = useMemo(() => {
        return renderChildren ? (
            <Foo />
        ) : null;
    });

    const BarComponent = renderChildren ? (
        <Bar />
    ) : null

    return (
        <Fragment>
            {FooComponent}
            {BarComponent}
        </Fragment>
    )
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I feel the same. useMemo can save on garbage collection if we are smart about the dependency array element. useMemo can quickly go out of hand if we abuse it by putting too many elements in the dependency array or elements that have a wide range of possible values. I don't think there is an eslint rule that can help us in this case but I feel useMemo has more potential that we are not using at this point in our code base.

Copy link
Contributor Author

@jimbo jimbo Feb 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the difference we're talking about, I think. Which of these is "more performant"/"better"?

I feel useMemo has more potential that we are not using at this point in our code base.

Yes, I understand. The difference here is negligible—it's one createElement() call. The benefit is not zero, but it's insignificant.

const MyComponent = props => {
  // this is also a `createElement` call
  // should we memoize it too?
  return <div>{props.text}</div>
}

If we wanted to realize the full potential of memoization, we could reproduce PureComponent.

// let's create a custom hook
// it'll only re-render when props change
const usePureComponent = (Component, props) => useMemo(
  () => <Component {...props} />,
  [props]
)

// looks pure to me
const MyComponent = props => {
  return <div>{props.text}</div>
}

// neat
export default const pure = props => usePureComponent(MyComponent, props)

This is cool, but it's overkill. We're not gaining much from the memoization, and now we're doing something that's pretty unusual. So this is where we draw the line.

We use it to avoid identities changing across renders, and as an early optimization when mapping props to elements in a loop, but not for literally every createElement call that happens to be amenable to memoization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But seriously, imagine a toggleable element that we expect a user to interact with quite often. Shouldn't we memoize the state to avoid re-creating the component unnecessarily?

Situationally, any time you have a particularly complex tree, you can consider memoizing it to improve the render performance. Toggling doesn't factor into it much, though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A question @revanth0212 had in the office was why not just memoize all the time since you may not know if a tree is complex or not.

<h3>There are no items in your cart.</h3>
);

const priceAdjustments = hasItems ? <PriceAdjustments /> : null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. Can we use useMemo? At most we will only be caching 2 versions of priceAdjustments either the component or null.

@m2-community-project m2-community-project bot moved this from Review in Progress to Changes Requested in Pull Request Progress Feb 27, 2020
@m2-community-project m2-community-project bot moved this from Changes Requested to Review in Progress in Pull Request Progress Feb 27, 2020
@jimbo jimbo added the version: Patch This changeset includes backwards compatible bug fixes. label Feb 27, 2020
@dpatil-magento dpatil-magento merged commit a7ae736 into develop Mar 2, 2020
@dpatil-magento dpatil-magento deleted the jimbo/conditionals branch March 2, 2020 17:17
@m2-community-project m2-community-project bot moved this from Review in Progress to Done in Pull Request Progress Mar 2, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg:venia-ui version: Patch This changeset includes backwards compatible bug fixes.
Development

Successfully merging this pull request may close these issues.

None yet

5 participants