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

focus and blur aren't callable when refs are passed into TextInputFocusable #2786

Closed
jasperhuangg opened this issue May 11, 2021 · 1 comment · Fixed by #2800
Closed

focus and blur aren't callable when refs are passed into TextInputFocusable #2786

jasperhuangg opened this issue May 11, 2021 · 1 comment · Fixed by #2800
Assignees
Labels
Daily KSv2 DeployBlockerCash This issue or pull request should block deployment Engineering Improvement Item broken or needs improvement. Reviewing Has a PR in review

Comments

@jasperhuangg
Copy link
Contributor

jasperhuangg commented May 11, 2021

If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!


Expected Result:

When we pass a ref to a TextInputFocusable, focus and blur should be callable as they are basic functionality of a text input.

Actual Result:

When we call focus or blur here and here respectively, they aren't callable, even though we're passing in a ref to the TextInputFocusable here.

Action Performed:

  1. Navigate to staging.expensify.cash
  2. Open a conversation.
  3. Attempt to open the emoji picker via the report action compose box. Notice how it doesn't open, and that a console error is logged.

Screen Shot 2021-05-11 at 2 45 20 PM

Workaround:

For places where we use programmatically focus or blur TextInputFocusable, it's broken.

Platform:

Web
iOS
Android
Desktop App
Mobile Web

Version Number:
Logs: https://stackoverflow.com/c/expensify/questions/4856
Notes/Photos/Videos: Any additional supporting documentation
Expensify/Expensify Issue URL:

View all open jobs on Upwork

@jasperhuangg jasperhuangg added DeployBlockerCash This issue or pull request should block deployment Engineering Daily KSv2 Improvement Item broken or needs improvement. labels May 11, 2021
@jasperhuangg jasperhuangg changed the title focus and blur are undefined when refs are passed into TextInputFocusable focus and blur aren't callable when refs are passed into TextInputFocusable May 11, 2021
@HorusGoul
Copy link
Contributor

HorusGoul commented May 11, 2021

I got it working locally. The problem with the ref is related to the withLocalize and withOnyx HOCs. These HOCs are not forwarding the ref properly, so I've added a log statement to see what's inside the ref. Here's what it logged:

Console output

image

As you can see, it's logging a withOnyx component. That shouldn't happen, but it makes sense considering that the withOnyx hook doesn't forward the ref. Take a look at the code, and you'll find that React.forwardRef is not being used.

A similar thing happens with the withLocalize HOC because, again, we're not forwarding the ref correctly. We should pass it as ref, not as forwardedRef to the child of the HOC component. Take a look at this code extracted from the React Docs:

Example code from the React Docs
function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      // Assign the custom prop "forwardedRef" as a ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // Note the second param "ref" provided by React.forwardRef.
  // We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
  // And it can then be attached to the Component.
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

And here's our withLocalize HOC, with the fix applied:

withLocalize with the fix applied
function withLocalizeHOC(WrappedComponent) {
    const WithLocalize = (props) => {
        const translations = {
            translate: (phrase, variables) => translate(props.preferredLocale, phrase, variables),
            numberFormat: (number, options) => numberFormat(props.preferredLocale, number, options),
            timestampToRelative: timestamp => DateUtils.timestampToRelative(props.preferredLocale, timestamp),
            timestampToDateTime: (timestamp, includeTimezone) => DateUtils.timestampToDateTime(
                props.preferredLocale,
                timestamp,
                includeTimezone,
            ),
            toLocalPhone: number => toLocalPhone(props.preferredLocale, number),
            fromLocalPhone: number => fromLocalPhone(props.preferredLocale, number),
        };
        return (
            <WrappedComponent
                // eslint-disable-next-line react/jsx-props-no-spreading
                {...props}
+               ref={props.forwardedRef}
                translate={translations.translate}
                numberFormat={translations.numberFormat}
                timestampToRelative={translations.timestampToRelative}
                timestampToDateTime={translations.timestampToDateTime}
                toLocalPhone={translations.toLocalPhone}
                fromLocalPhone={translations.fromLocalPhone}
            />
        );
    };
    WithLocalize.displayName = `WithLocalize(${getComponentDisplayName(WrappedComponent)})`;
    WithLocalize.propTypes = {
        preferredLocale: PropTypes.string,
+       forwardedRef: PropTypes.func,
    };
    WithLocalize.defaultProps = {
        preferredLocale: 'en',
+       forwardedRef: undefined,
    };
    return React.forwardRef((props, ref) => (
        // eslint-disable-next-line react/jsx-props-no-spreading
        <WithLocalize {...props} forwardedRef={ref} />
    ));
}

Solutions

a) One of them would be to fix both HOCs to pass the refs properly, but this may lead to unexpected things breaking.

b) The other one, which would be faster and safer, is to remove the HOCs from this component and pass it either the translated strings it needs or the translate function, decoupling it from the app's global state and localization system to keeping it as a presentational component.


I'm more inclined to apply the solution (b) now and explore (a) in the future to avoid this from happening again. Let me know what you think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Daily KSv2 DeployBlockerCash This issue or pull request should block deployment Engineering Improvement Item broken or needs improvement. Reviewing Has a PR in review
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants