Skip to content

Conversation

@AndrewMusgrave
Copy link
Member

@AndrewMusgrave AndrewMusgrave commented Aug 3, 2022

WHY are these changes introduced?

Fixes https://github.com/Shopify/web/issues/70102

A lot of time is spent recalculating styles, that becomes more significant the more complex the page is.

WHAT is this pull request doing?

Using RFA to delay checking node attributes, to avoid reflows.

Screenies

Before After
Screen Shot 2022-08-03 at 3 21 21 PM Screen Shot 2022-08-03 at 3 19 27 PM

The self time was reduced from ~29ms to ~9ms

How to 🎩

  • Load playground code
  • Open the performance tab in your dev tools
  • Press reload & wait till it finishes
  • Look at the Bottom-up tab for recalculate styles
  • Repeat on main (or before screenshot)

Playground code below

Copy-paste this code in playground/Playground.tsx:
import React from 'react';

import {Page, IndexTable, TextStyle, Card, useIndexResourceState} from '../src';

export function Playground() {
  return (
    <Page title="Playground" primaryAction={{content: 'Save', disabled: true}}>
      <IndexTableWithAllElementsExample />
    </Page>
  );
}

function IndexTableWithAllElementsExample() {
  const customers = createCustomers(50);
  const resourceName = {
    singular: 'customer',
    plural: 'customers',
  };

  const {selectedResources, allResourcesSelected, handleSelectionChange} =
    useIndexResourceState(customers);

  const promotedBulkActions = [
    {
      content: 'Edit customers',
      onAction: () => console.log('Todo: implement bulk edit'),
    },
  ];
  const bulkActions = [
    {
      content: 'Add tags',
      onAction: () => console.log('Todo: implement bulk add tags'),
    },
    {
      content: 'Remove tags',
      onAction: () => console.log('Todo: implement bulk remove tags'),
    },
    {
      content: 'Delete customers',
      onAction: () => console.log('Todo: implement bulk delete'),
    },
  ];

  const rowMarkup = customers.map(
    ({id, name, location, orders, amountSpent}, index) => (
      <IndexTable.Row
        id={id}
        key={id}
        selected={selectedResources.includes(id)}
        position={index}
      >
        <IndexTable.Cell>
          <TextStyle variation="strong">{name}</TextStyle>
        </IndexTable.Cell>
        <IndexTable.Cell>{location}</IndexTable.Cell>
        <IndexTable.Cell>{orders}</IndexTable.Cell>
        <IndexTable.Cell>{amountSpent}</IndexTable.Cell>
      </IndexTable.Row>
    ),
  );

  return (
    <Card>
      <IndexTable
        resourceName={resourceName}
        itemCount={customers.length}
        selectedItemsCount={
          allResourcesSelected ? 'All' : selectedResources.length
        }
        onSelectionChange={handleSelectionChange}
        hasMoreItems
        bulkActions={bulkActions}
        promotedBulkActions={promotedBulkActions}
        lastColumnSticky
        headings={[
          {title: 'Name'},
          {title: 'Location'},
          {title: 'Order count'},
          {title: 'Amount spent', hidden: false},
        ]}
      >
        {rowMarkup}
      </IndexTable>
    </Card>
  );
}

function createCustomers(num: number) {
  const customers = [];
  for (let i = 0; i < num; i++) {
    customers.push(createCustomer(i));
  }

  return customers;
}
const randomStr = () => `${(Math.random() + 1).toString(36).substring(7)}`;

function createCustomer(id: number) {
  return {
    id: `${id}`,
    url: `customers/${id}`,
    name: randomStr(),
    location: randomStr(),
    orders: 20,
    amountSpent: randomStr(),
  };
}

Demo 🎩 gif

Screen Recording 8-11-2022 at 2 35 PM

🎩 checklist

@AndrewMusgrave
Copy link
Member Author

/snapit

@github-actions
Copy link
Contributor

github-actions bot commented Aug 3, 2022

size-limit report 📦

Path Size
polaris-react-cjs 201.16 KB (+0.01% 🔺)
polaris-react-esm 128.98 KB (+0.01% 🔺)
polaris-react-esnext 183.11 KB (+0.01% 🔺)
polaris-react-css 40.62 KB (0%)

@AndrewMusgrave AndrewMusgrave force-pushed the am-index-table-reflow-perf branch from 69eabd5 to cc3375c Compare August 3, 2022 18:33
@AndrewMusgrave
Copy link
Member Author

/snapit

@github-actions
Copy link
Contributor

github-actions bot commented Aug 3, 2022

🫰✨ Thanks @AndrewMusgrave! Your snapshot has been published to npm.

Test the snapshot by updating your package.json with the newly published version:

yarn add @shopify/[email protected]

@AndrewMusgrave AndrewMusgrave force-pushed the am-index-table-reflow-perf branch from 59f6eb6 to b02e8f6 Compare August 3, 2022 19:23
@AndrewMusgrave
Copy link
Member Author

/snapit

@github-actions
Copy link
Contributor

github-actions bot commented Aug 3, 2022

🫰✨ Thanks @AndrewMusgrave! Your snapshot has been published to npm.

Test the snapshot by updating your package.json with the newly published version:

yarn add @shopify/[email protected]

export interface TableHeadingRect {
offsetWidth: number;
offsetLeft: number;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Don't forget to hide whitespace!

Comment on lines -204 to -233
SIXTY_FPS,
{leading: true, trailing: true, maxWait: SIXTY_FPS},
Copy link
Member Author

Choose a reason for hiding this comment

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

When these values are omitted debounce will fallback to RAF.

Sixty fps is aprox 16.7ms which is how often RAF will fire. Rather than estimate when the frame will be, let's use it!

@AndrewMusgrave AndrewMusgrave force-pushed the am-index-table-reflow-perf branch from b02e8f6 to 767d63e Compare August 3, 2022 19:51
}
const handleCanScrollRight = useCallback(
() =>
debounce(() => {
Copy link
Member Author

Choose a reason for hiding this comment

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

This'll default to raf

// eslint-disable-next-line react-hooks/exhaustive-deps
const handleResize = useCallback(
debounce(() => {
if (position !== 0) return;
Copy link
Member Author

Choose a reason for hiding this comment

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

We only need to calculate the offset once, not for every checkbox

@AndrewMusgrave AndrewMusgrave force-pushed the am-index-table-reflow-perf branch from 767d63e to 1f95bf5 Compare August 3, 2022 21:41
}
// eslint-disable-next-line react-hooks/exhaustive-deps
const handleCanScrollRight = useCallback(
debounce(() => {
Copy link
Member Author

Choose a reason for hiding this comment

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

This'll default to raf

if (!checkboxNode.current) return;
// eslint-disable-next-line react-hooks/exhaustive-deps
const handleResize = useCallback(
debounce(() => {
Copy link
Member Author

Choose a reason for hiding this comment

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

This'll default to raf

// eslint-disable-next-line react-hooks/exhaustive-deps
const handleResize = useCallback(
debounce(() => {
if (position !== 0 || !checkboxNode.current) return;
Copy link
Member Author

Choose a reason for hiding this comment

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

We only need to set the offset once, not for every checkbox!

});
setGetBoundingClientRect(200);

act(() => {
Copy link
Member Author

@AndrewMusgrave AndrewMusgrave Aug 3, 2022

Choose a reason for hiding this comment

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

act is needed since component state changes outside trigger (which automatically uses act)

@AndrewMusgrave
Copy link
Member Author

/snapit

@github-actions
Copy link
Contributor

github-actions bot commented Aug 3, 2022

🫰✨ Thanks @AndrewMusgrave! Your snapshot has been published to npm.

Test the snapshot by updating your package.json with the newly published version:

yarn add @shopify/[email protected]

@AndrewMusgrave AndrewMusgrave force-pushed the am-index-table-reflow-perf branch from df45c80 to 7a5fde9 Compare August 11, 2022 18:17
@AndrewMusgrave AndrewMusgrave marked this pull request as ready for review August 11, 2022 20:27
Copy link
Member

@chloerice chloerice left a comment

Choose a reason for hiding this comment

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

This is a huge improvement, thanks Andrew!! 🚀 🚀 🚀

) {
return;
}
// eslint-disable-next-line react-hooks/exhaustive-deps

Choose a reason for hiding this comment

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

I'm confused by this, why not pass an inline function that returns the debounce? I see it done in both ways in this file, what's the difference?

Choose a reason for hiding this comment

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

ah I get it, the difference between this executing immediately vs calling it directly.

Copy link

@sethomas sethomas left a comment

Choose a reason for hiding this comment

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

kk awesome, I did 10 tests each for before and after for the Recalculate Style:

Before average: 63.28ms
After average: 41.61ms

I wasn't able to see massive gains or numbers like you mentioned, but I could also have been doing something wrong.

@AndrewMusgrave
Copy link
Member Author

I wasn't able to see massive gains or numbers like you mentioned, but I could also have been doing something wrong.

Let's take a look durning our pairing tomorrow. 60 is still double what ive been seeing so it'll be interesting to see where it's coming from 🤔

@sethomas
Copy link

We resolved it! Nice! Issues that I was doing:

  1. I was testing the entire page with the storybook scope/wrappers vs the individual playground component
  2. there were some chrome extensions on the page that was altering the performance.

Thanks for looking with me about what the discrepancies were.

Copy link
Contributor

@mrcthms mrcthms left a comment

Choose a reason for hiding this comment

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

Nice perf gains!

@AndrewMusgrave AndrewMusgrave force-pushed the am-index-table-reflow-perf branch from 512540b to d9b5e41 Compare September 1, 2022 17:19
@AndrewMusgrave AndrewMusgrave merged commit 90f3254 into main Sep 1, 2022
@AndrewMusgrave AndrewMusgrave deleted the am-index-table-reflow-perf branch September 1, 2022 17:48
@github-actions github-actions bot mentioned this pull request Sep 1, 2022
alex-page pushed a commit that referenced this pull request Sep 2, 2022
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.


# Releases
## @shopify/[email protected]

### Minor Changes

-   [#6890](#6890) [`267e1a9bd`](267e1a9) Thanks [@alex-page](https://github.com/alex-page)! - Add deprecated scss API to stylelint-polaris and use regex tests in the config

## @shopify/[email protected]

### Patch Changes

-   [#6840](#6840) [`90f325460`](90f3254) Thanks [@AndrewMusgrave](https://github.com/AndrewMusgrave)! - Removed additional reflows from IndexTable

## [email protected]

### Minor Changes

-   [#7074](#7074) [`ddfacd855`](ddfacd8) Thanks [@alex-page](https://github.com/alex-page)! - Replace data/\*.json files with build time .cache/site.json


-   [#7082](#7082) [`8ebb9fc3c`](8ebb9fc) Thanks [@selenehinkley](https://github.com/selenehinkley)! - Large edit of /contributing


-   [#7087](#7087) [`43ea7e5d5`](43ea7e5) Thanks [@alex-page](https://github.com/alex-page)! - Add automation to generate .cache/nav.json

### Patch Changes

-   [#7037](#7037) [`7dafdee00`](7dafdee) Thanks [@alex-page](https://github.com/alex-page)! - Move header logic to the API and out of next.config.js


-   [#7075](#7075) [`5933fc547`](5933fc5) Thanks [@alex-page](https://github.com/alex-page)! - Run gen-assets on build of website

-   Updated dependencies \[[`90f325460`](90f3254)]:
    -   @shopify/[email protected]

## [email protected]

### Patch Changes

-   Updated dependencies \[[`90f325460`](90f3254)]:
    -   @shopify/[email protected]

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants