Skip to content

Conversation

@hgray-instawork
Copy link
Collaborator

Optimizing style and props creation by replacing createProps and createStyleProp with hooks. After converting several class functions into functional components, the hooks were added. Additional optimizations include:

  • destructuring props and options to help with memoization
  • improved overall memoization through use of useMemo and useCallback. Grouped options and values together where possible to reduce overhead.

See commits for full breakdown.

Asana

@hgray-instawork hgray-instawork force-pushed the hardin/render-style-hooks branch from fb45336 to 2611fd9 Compare June 10, 2025 19:35
Copy link
Collaborator

@flochtililoch flochtililoch left a comment

Choose a reason for hiding this comment

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

I'm not sure if you did a pass already to address what we discussed yesterday, but re-adding my feedback here (with some additional feedback):

I think we should remove the memoization for:

  • simple attribute getters or objects accessor
  • any low usage components (i.e. all form elements, webview)
    Right now, memoization for these adds a lot of the verbosity in code, and I doubt they provide a real gain. I think memoization makes sense in some places, for heavy usage components like text, or view, and when there's computation involved (for loop, object creation etc.).

Going one step further in this migration, could we:

  • define a return type for the new hooks, and remove use of any when these are used
  • convert createTestProps to useTestProps, have it do internal memoization on element
  • remove the code within legacy create* functions and have these delegate to their equivalent hook function (granted we would also need to silence the ESLint warning about not being in a React component)

And as future tasks (low priority but we should track):

  • I see HvTextField disables rules of hooks linting rule, or does unnecessary re-creation of objects (elementProps is spread into p, which is spread again when applied to the component instance)
  • I noted many components (hv-switch, hv-select-single) were reading attributes directly from element, but we're also able to get these attributes when calling useProps, so we should probably refactor to simplify and remove duplication.

options: HvComponentOptions,
) => {
const { attributes } = element;
const { focused, pressed, pressedSelected, selected, styleAttr } = options;
Copy link
Collaborator

Choose a reason for hiding this comment

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

minor: it seems there's no need to de-structure this object and recreate it on the next line

Comment on lines +29 to +31
const component = useMemo(() => React.createElement(Image, componentProps), [
componentProps,
]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

component is changing from the return value of React.createElement (previous implementation) to a functional component that returns the returned value of React.createElement (new implementation). I assume this is unintentional, as this changes the produced tree, and I wonder if we need this.

* Component used to render the Cancel/Done buttons in the picker modal.
*/
export default (props: Props) => {
// eslint-disable-next-line react/destructuring-assignment
Copy link
Collaborator

Choose a reason for hiding this comment

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

minor/may be addressed in a later PR: I think we should disable that rule in the eslint config since it's so common to do prop destructuring in FC

const color = props['activity-indicator-color'] || '#8d9494';
const loadBehavior = props['show-loading-indicator'];
let injectedJavaScript = props['injected-java-script'];
const webViewProps = useMemo(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I feel this memoization isn't very useful: it does not wrap any complex computation, and the entirety of what's generated here comes from componentProps which is already heavily memoized within useProps.

if (props.element.getAttribute('hide') === 'true') {
// eslint-disable-next-line react/destructuring-assignment
const { element, onUpdate, options, stylesheets } = props;
// eslint-disable-next-line react-hooks/rules-of-hooks
Copy link
Collaborator

Choose a reason for hiding this comment

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

which warning are we disabling here?

styleAttr: 'field-text-style',
});

const { testID, accessibilityLabel } = useMemo(
Copy link
Collaborator

Choose a reason for hiding this comment

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

per my comment re createTestPropsuseTestProps, we shouldn't need the memoization here once applied

[element],
);

const placeholderTextColor: DOMString | null | undefined = useMemo(
Copy link
Collaborator

Choose a reason for hiding this comment

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

this feels overkill too

Comment on lines +136 to +144
const needsEmptyOption = useMemo(
() => pickerItems.length > 0 && pickerItems[0].getAttribute('value') !== '',
[pickerItems],
);

const placeholder = useMemo(
() => element.getAttribute('placeholder') || undefined,
[element],
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

same here

// Gets all of the <picker-item> elements. All picker item elements
// with a value and label are turned into options for the picker.
const children = useMemo(() => {
const items = pickerItems.reduce<Array<React.ReactNode>>((acc, item) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

did we need to change the logic from a map to an array reduce?

this.props.element.getAttribute('preformatted') === 'true',
},
// TODO: Replace with <HvChildren>
const component = useMemo(
Copy link
Collaborator

Choose a reason for hiding this comment

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

this memo will be re-created anytime the props changes - wouldn't we be better off memoizing the entire component?

@hgray-instawork hgray-instawork marked this pull request as draft June 12, 2025 20:38
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.

3 participants