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 the update of link and suggestions in LinkControl #32320

Merged
merged 6 commits into from
Nov 5, 2021

Conversation

ralucaStan
Copy link
Contributor

@ralucaStan ralucaStan commented May 28, 2021

When switching between two anchors in the same text block, the text input from the edit link window will not show the correct value.
See video with bug here: https://www.loom.com/share/d18dba4f4c0c43b0adb33f943e77ce0f

Description

When switching between two links, the LinkControl component does not unmount.
The value prop will change but the component does not react to this change and does
not show the updated link & suggestions.

This PR does 2 things:

  • it accounts for the value change inside of LinkControl and updates it's internal state
  • it modifies the update of the suggestions when the value changes. The component only handled a new empty value, showing the initial suggestions if the user was to remove the value inside the input. But because the value can change and not be empty, we need to update the suggestions for this new non-empty value.

How has this been tested?

  • go to a page and add two links in the same text block
  • go to the first link and click it to see the edit link window
  • click the Edit button and see the text input with the URL and the suggestions list
  • without clicking anything else, click the second link
  • observe the values in the text input and the suggestion

Screenshots

Types of changes

Bug fix

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • I've tested my changes with keyboard and screen readers.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR (please manually search all *.native.js files for terms that need renaming or removal).

@ralucaStan ralucaStan requested a review from ellatrix as a code owner May 28, 2021 13:15
@github-actions
Copy link

👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @ralucaStan! In case you missed it, we'd love to have you join us in our Slack community, where we hold regularly weekly meetings open to anyone to coordinate with each other.

If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information.

@github-actions github-actions bot added the First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository label May 28, 2021
@getdave getdave requested review from getdave and removed request for ellatrix August 9, 2021 12:00
@getdave
Copy link
Contributor

getdave commented Aug 9, 2021

Ill take a look

@getdave getdave added the [Feature] Link Editing Link components (LinkControl, URLInput) and integrations (RichText link formatting) label Aug 9, 2021
Copy link
Contributor

@getdave getdave left a comment

Choose a reason for hiding this comment

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

Update: just had a thought here. I think the perceived "delay" in the update is due to the URLInput re-fetching the search suggestions.

Screen.Capture.on.2021-08-12.at.14-48-25.mov

It's actually not doing a network request because the value.url is identified as being "URL-like" so it ends up in a direct entry handler

export const handleDirectEntry = ( val ) => {
let type = 'URL';
const protocol = getProtocol( val ) || '';
if ( protocol.includes( 'mailto' ) ) {
type = 'mailto';
}
if ( protocol.includes( 'tel' ) ) {
type = 'tel';
}
if ( startsWith( val, '#' ) ) {
type = 'internal';
}
return Promise.resolve( [
{
id: val,
title: val,
url: type === 'URL' ? prependHTTP( val ) : val,
type,
},
] );
};

This handler resolves with a Promise. That's why we see the delay in the update.

I guess we could:

  1. Find a way to reset the suggestions before we do the "fetching" logic.
  2. Deliberating introduce a delay in the handleDirectEntry promise resolution to force the UI to go into a loading state.

Thanks for catching this bug and for the PR. I encounter the same problem working on #33849 and it's really important we get it solved.

I tested it and for the most part it seemed to work well. However, it is possible to observe the component updating after the fact once you switch to the 2nd link.

In the video below you can clearly observe the link in the input changing from X -> Y as I switch between them. Ideally this wouldn't happen.

Screen.Capture.on.2021-08-12.at.14-28-59.mp4

I think perhaps we should consider force remounting the component based on the value of the URL.

We could try to do this by adding a key prop to the component and assigning it's value to value.url. That way if the value changes the component will force remount.

I'd like to get a second opinion on this - perhaps from @kevin940726?

The reason this is not happening at the moment is - I believe - due to the fact that Popover doesn't remount. As a result any child component doesn't re-mount either. Thus we get stale states.

You'll know this is true because if you fully unmount <LinkControl> (for example by clicking away from the text entirely and then clicking back on the next link) then all the state will be updated correctly.

Comment on lines 89 to 91
! this.props.disableSuggestions &&
! this.isUpdatingSuggestions &&
! this.props.__experimentalShowInitialSuggestions
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you explain this change and why it's necessary? This change removes documentation in terms of:

  1. The code comment explaining what's happening.
  2. A well named function call which self-documents the check logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The intention here was to bypass shouldShowInitialSuggestions, which was returning false because of __experimentalShowInitialSuggestions, and just copy its logic regarding the display of suggestions.
I see now that there was a misunderstanding from my side regarding what initial suggestions meant so the ! this.props.__experimentalShowInitialSuggestions part of my change does not make sense.

I am not sure how to proceed further here as I don't understand why __experimentalShowInitialSuggestions is set to false inside shouldShowInitialSuggestions

__experimentalShowInitialSuggestions = false,

Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure how to proceed further here as I don't understand why __experimentalShowInitialSuggestions is set to false inside shouldShowInitialSuggestions

As far as I can see it isn't necessarily being set to false here. Rather false is used the default value if __experimentalShowInitialSuggestions is not provided as a prop (ie: is undefined). See MDN for more details.

I still don't quite understand why you need to bypass the this.shouldShowInitialSuggestions() logic here? What was it about that which was breaking the fix you've tried to introduce here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @getdave,
I am sorry for the late reply.

What was it about that which was breaking the fix you've tried to introduce here?

You can see in the video below the behavior if I don't bypass this.shouldShowInitialSuggestions():
when I click on the second link I see the suggestions from the first link.
https://www.loom.com/share/79dedfab3b6942458d2b6fa6d2c9e34b

Let's look at what this.shouldShowInitialSuggestions() returns:

return 
(
! this.isUpdatingSuggestions && // suggestions aren't loaded
__experimentalShowInitialSuggestions && 
! ( value && value.length ) && // there is no value in the input
! ( suggestions && suggestions.length ) // there are no suggestions loaded
)

Please note that this would return true only when the user selects a text and clicks Add link.

In our case, when clicking the second link, this.shouldShowInitialSuggestions() always returns false because:

  • there is a value in the input, namely the 2nd link
  • there are suggestions available, from the 1st link.

What happens is that when I open the 1st link, the suggestions are updated with the onFocus callback. When clicking on the 2nd link, onFocus is no longer called and the suggestions aren't updated in componentDidUpdate because of this.shouldShowInitialSuggestions

packages/block-editor/src/components/link-control/index.js Outdated Show resolved Hide resolved
packages/block-editor/src/components/link-control/index.js Outdated Show resolved Hide resolved
packages/block-editor/src/components/url-input/index.js Outdated Show resolved Hide resolved
@kevin940726
Copy link
Member

Yeah, I feel like using key to force a remount might be the best way forward if there's no performance issue. We can wrap <LinkControl> with a wrapper component so that it's not required for users to update them. Something like this:

function LinkControlWrapper(props) {
  return <LinkControl {...props} key={props.value} />
}

Not sure about the delay thing you mentioned though 😅.

Copy link
Contributor

@getdave getdave left a comment

Choose a reason for hiding this comment

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

I've thought about this some more and whilst this change doens't fully resolve the issue (as described in my previous review) it does move us towards a better state.

We can look to tackle the other lagging visual update of the search result in a follow up PR.

If you could address the feedback here then we can look to merge this.

Comment on lines 89 to 91
! this.props.disableSuggestions &&
! this.isUpdatingSuggestions &&
! this.props.__experimentalShowInitialSuggestions
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure how to proceed further here as I don't understand why __experimentalShowInitialSuggestions is set to false inside shouldShowInitialSuggestions

As far as I can see it isn't necessarily being set to false here. Rather false is used the default value if __experimentalShowInitialSuggestions is not provided as a prop (ie: is undefined). See MDN for more details.

I still don't quite understand why you need to bypass the this.shouldShowInitialSuggestions() logic here? What was it about that which was breaking the fix you've tried to introduce here?

packages/block-editor/src/components/url-input/index.js Outdated Show resolved Hide resolved
@getdave
Copy link
Contributor

getdave commented Aug 23, 2021

Yeah, I feel like using key to force a remount might be the best way forward if there's no performance issue.

Let's tackle this in a follow up PR. I believe this PR moves us towards a better state.

@getdave
Copy link
Contributor

getdave commented Sep 10, 2021

I haven't forgotten about this one. Just trying out some other options.

Copy link
Contributor

@getdave getdave left a comment

Choose a reason for hiding this comment

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

I've left one change that I think should be made but other than that I'm happy to approve this.

You'll need another careful rebase.

Thanks for your patience here. I've taken far too long to get to this.

packages/block-editor/src/components/url-input/index.js Outdated Show resolved Hide resolved
ralucaStan and others added 3 commits October 28, 2021 15:47
When switching between two links, the LinkControl component does not unmount.
The value prop will change but the component does not react to this change and does
not show the updated link & suggestions.
Trimming gets done in the function itself, since PR WordPress#35060.
@getdave
Copy link
Contributor

getdave commented Oct 28, 2021

@tellthemachines We'd also appreciate it if you could review this one 🙇

… string

This was removed by the first commits, but we need to take into consideration that we also want to show the initial suggestions when the new value is empty
@ralucaStan
Copy link
Contributor Author

ralucaStan commented Oct 28, 2021

I've left one change that I think should be made but other than that I'm happy to approve this.

You'll need another careful rebase.

Thanks for your patience here. I've taken far too long to get to this.

@getdave I have pushed the changes that I thing fix this issue and updated the description.
With my last change I handled the update of the suggestions when the value changes in the URLInput component. The component only handled a new empty value, showing the initial suggestions, if if the user was to remove the value inside the input (only when __experimentalShowInitialSuggestions=true). But because the value can change and not be empty, we need to update the suggestions for this new non-empty value case.

The branch is also rebased on the latest trunk

@tellthemachines this is ready for review. Ping me with anything

@@ -84,12 +87,19 @@ class URLInput extends Component {
}, 100 );
}

// Only attempt an update on suggestions if the input value has actually changed.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The component only handled a new empty value, showing the initial suggestions if the user was to remove the value inside the input. But because the value can change and not be empty, we need to update the suggestions for this new non-empty value.

*/
if ( value?.url ) {
setInternalInputValue( value.url );
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Account for the value change inside of LinkControl and updates it's internal state

Copy link
Contributor

@tellthemachines tellthemachines 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 working well ✅

Ideally the popover shouldn't open in edit mode at all, but I think #34742 addresses that, so let's get this one in to start with!

Copy link
Contributor

@getdave getdave left a comment

Choose a reason for hiding this comment

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

Great work. LGTM :shipit:

packages/block-editor/src/components/url-input/index.js Outdated Show resolved Hide resolved
@getdave getdave merged commit 8053b37 into WordPress:trunk Nov 5, 2021
@github-actions github-actions bot added this to the Gutenberg 11.9 milestone Nov 5, 2021
@getdave getdave added Backport to WP 6.7 Beta/RC Pull request that needs to be backported to the WordPress major release that's currently in beta [Type] Bug An existing feature does not function as intended labels Nov 9, 2021
@noisysocks noisysocks removed the Backport to WP 6.7 Beta/RC Pull request that needs to be backported to the WordPress major release that's currently in beta label Nov 10, 2021
@noisysocks
Copy link
Member

This doesn't need the back port label as it landed in 11.9.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Link Editing Link components (LinkControl, URLInput) and integrations (RichText link formatting) First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository [Type] Bug An existing feature does not function as intended
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants