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

Support paste verification #1106

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from

Conversation

puntope
Copy link
Contributor

@puntope puntope commented Nov 23, 2021

Changes proposed in this Pull Request:

Closes #1001 .

Added functionality in order to allow the user to paste the code inside the verification code control inputs.

Screenshots:

Screen.Recording.2021-11-23.at.10.33.53.mov

Changes

  • Mainly a refactor in verification Code Control
  • Added a new handlePaste function to handle the input in the case is a paste event
  • Added some tests

Detailed test instructions:

  1. Go to Onboarding Setup Dashboard or to Settings -> Edit Phone
  2. Edit the phone and click on Verify Phone Number
  3. Check you can paste digits on the inputs
  4. Check you can insert the input as normal
  5. You cannot insert letters or symbols, only digits.

Changelog entry

Improve accessibility in Verification Code Control

@puntope puntope requested a review from a team November 23, 2021 09:38
@puntope puntope removed the request for review from a team November 23, 2021 10:29
@puntope puntope changed the title Support paste verification WIP: Support paste verification Nov 23, 2021
@puntope puntope changed the title WIP: Support paste verification Support paste verification Nov 23, 2021
@puntope puntope requested a review from a team November 23, 2021 11:51
@ecgan ecgan assigned ecgan and puntope and unassigned ecgan Nov 23, 2021
Copy link
Member

@eason9487 eason9487 left a comment

Choose a reason for hiding this comment

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

Sorry I haven't looked at the .test.js part. This review only comments on the implementation first.

  1. 🚧 When pasting, the <input> value of the last digit is incorrectly displayed back to the previous value till losing focus.
    Kapture 2021-11-24 at 17 33 04
    Kapture 2021-11-24 at 17 35 07
  2. ❓ When the length of the pasted codes are not long enough to fill to the last <input>, it should be better to move the focus to the next <input> to be entered
    Kapture 2021-11-24 at 17 42 31

Comment on lines 136 to 142
const handlePaste = ( e ) => {
const { idx, value } = getEventData( e );

onCodeChange( toCallbackData( nextDigits ) );
}
// only allow n digits, from the current idx position until the end
const newDigits = [
...value.replace( /\D/g, '' ).substr( 0, DIGIT_LENGTH - idx ),
];
Copy link
Member

Choose a reason for hiding this comment

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


We don't need to handle content other than plain text. Not sure why it needs to use onPaste event? The same newDigits value could be got in the original onInput handler by

const handleInput = ( e ) => {
	const { value, dataset } = e.target;
	const newDigits = value
		.replace( /\D/g, '' )
		.substr( cursorRef.current, DIGIT_LENGTH - idx );

	// ...omitted
};

I think this could simplify the processing of getting content from onPaste, and even further simplify other changes by using the original handleInput to handle a 1 to DIGIT_LENGTH length array of digits.

@eason9487
Copy link
Member

FYI - If need to get more context from the previous relevant discussions, you can find them in these PRs:

@puntope
Copy link
Contributor Author

puntope commented Nov 24, 2021

2. ❓ When the length of the pasted codes are not long enough to fill to the last <input>, it should be better to move the focus to the next <input> to be entered

Agree! 👍

Fixed in e37f3b7

@puntope
Copy link
Contributor Author

puntope commented Nov 24, 2021

  1. 🚧 When pasting, the <input> value of the last digit is incorrectly displayed back to the previous value till losing focus.

This is definitely working for me. 😕 Could you double-check?

Note: At some point in the dev process, it wasn't working, that is why I added the statement updateInputRefs( digits );

@eason9487
Copy link
Member

  1. 🚧 When pasting, the <input> value of the last digit is incorrectly displayed back to the previous value till losing focus.

This is definitely working for me. 😕 Could you double-check?

Tried with npm install, restarting npm start, and the latest version of Chrome, the problem can be reproduced on my end. A new finding is that it only occurs during the cooling countdown, which means that it probably occurs when any re-renders are triggered from upper layers.

Kapture 2021-11-25 at 11 05 10

@puntope
Copy link
Contributor Author

puntope commented Nov 25, 2021

A new finding is that it only occurs during the cooling countdown, which means that it probably occurs when any re-renders are triggered from upper layers.

First of all, thanks for catching this @eason9487! 🙏 My manual test was wrong cos my counter was off due request limit. Now I was able to reproduce and fix the error.

the problem

Bug causes:

Testing deeper I can confirm the problem is related to the way input-field handles the internal state on focus after each render. The value is not updated in the render until the input lost focus. I could confirm that everything works fine replacing that ìnput-field` with a native input.

On single-digit insertion, we don't have that problem since we move the focus to the next input box. Hence, the previous one is updated correctly after losing focus.

Solution:

I replaced now the inputRefs update solution with a new state solution, in order to create an extra tick and sync the internal state for the input-field

Besides that, I refactored a bit the resetNeedle effects in order to avoid sync conflicts.

Let me know if it works now :)

Tested:

  • Works with counter running both insert and paste, full and partial pastes.
  • Change on resetNeedle reset the digits and the focus. (Click on resend, change method, request error)
  • Works with counter stoped both insert and paste, full and partial pastes.
  • Verification code value in parent component is updated.
  • No es-lint warns
  • Unit tests passed

@eason9487
Copy link
Member

Thanks for the updates!

However, I'm sorry that it brings new issues and some previous issues are still unresolved.

  1. 🚧 The original automatic focus for input cursor is still not working when no entered verification codes.
    Switch verification method
    Kapture 2021-11-25 at 18 12 40
    Resend
    Kapture 2021-11-25 at 18 11 54
  2. 🚧 Sometimes the focus stays at the pasting <input> with previous value when pasting filled <input>s. Steps I tried
    1. Paste 123456 on the first <input>
    2. Copy 886 and pate on the third <input>
    3. The displaying values are changed to 123866, but I expected it would be 128866 and the focus should be moved to the last <input>.
      Kapture 2021-11-25 at 18 17 17
  3. 🚧 When pasting on the last , the problem of the display value is out of sync with the internal state still exists till losing focus.
    Kapture 2021-11-25 at 18 23 00

Copy link
Member

@eason9487 eason9487 left a comment

Choose a reason for hiding this comment

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

There're two new critical issues after adding new commits.

@puntope
Copy link
Contributor Author

puntope commented Nov 25, 2021

However, I'm sorry that it brings new issues and some previous issues are still unresolved.

No sorry, thanks to you for checking this! Better you than the users :) Thank you for being so thorough in the reviews!

🚧 The original automatic focus for input cursor is still not working when no entered verification codes.
Switch verification method

Fixed here d20faae

🚧 Sometimes the focus stays at the pasting with previous value when pasting filled s. Steps I tried
Paste 123456 on the first
Copy 886 and pate on the third
The displaying values are changed to 123866, but I expected it would be 128866 and the focus should be moved to the last .

Fixed here d20faae

🚧 When pasting on the last , the problem of the display value is out of sync with the internal state still exists till losing focus.

Fixed here d20faae

@puntope
Copy link
Contributor Author

puntope commented Nov 25, 2021

The useRef removed by 875f05e is not unnecessary. It was used for this reason.

Restored!

@puntope
Copy link
Contributor Author

puntope commented Nov 25, 2021

💅

Use === to check whether the values in two different arrays are equal is not appropriate.

Added toString() strategy. Other alternatives are...

_.isEqual (lodash)
Just a loop
Stringify

Copy link
Member

@eason9487 eason9487 left a comment

Choose a reason for hiding this comment

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

📜
Due to the internal state management of <InputControl>, the implementation of this component has to use some workaround for the focus controlling of input cursor. But the fixing result after several rounds of code review makes the implementation start to become too complex. Even more, workarounds have to be used at more places because of other side effects caused by the added workarounds.

The key problem is when a <InputControl> has the focus, updating the value prop would not affect the actually displaying content on the <input>, and even update via the value property of the real <input> DOM node could temporarily show the wanted content, it will be still recovered back to the content of passing value prop after any re-render occurs.

When we want to set the focus on an unfocused <InputControl>, which is also updating its display content, this state conflict happened. To deal with this, it could be resolved by updating the value prop and yielding the focus move to the next tick of re-render.

And this case will only happen in the first (when reset) and the last (when pasted) <InputControl>. Moving the workaround into the maybeMoveFocus function with a bit of magic could make the implementation simpler and clearer.

const maybeMoveFocus = ( targetIdx ) => {
	const validIdx = Math.max( Math.min( targetIdx, DIGIT_LENGTH - 1 ), 0 );
	const node = inputsRef.current[ validIdx ];

	if ( targetIdx === validIdx ) {
		node.focus();
	} else {
		setTimeout( () => node.focus() );
	}
};

Then we could call maybeMoveFocus( -1 ); when resetting, and maybeMoveFocus( DIGIT_LENGTH ); when moving focus to the last. With this method, it will need to change most other implementations. I made the alternative with code comments here 8db0834...707e15b. Probably we could consider this solution.

? handlePaste( e )
: handleInput( e );

setFocus( nextFocusIdx );
Copy link
Member

Choose a reason for hiding this comment

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

🚧

The focus state may be out of sync with the actual focusing index of <input> nodes, and further causes the input to get stuck due to all deps have not been changed here:

useEffect( () => {
maybeMoveFocus( focus );
}, [ digits, resetNeedle, focus ] );

Steps:

  1. Enter a number in the first <input>
  2. Move back to the first <input> by the ← key or mouse
  3. Enter the same number as step 1
  4. I expect it would move to the next input but it gets stuck

Kapture 2021-11-29 at 17 57 06

const digit = value.substr( cursorRef.current, 1 ).replace( /\D/, '' );

// If that char is not a digit, then clear the input to empty.
Copy link
Member

Choose a reason for hiding this comment

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


The meaning of this code comment is different from the if ( digit !== value ) condition and its processing below.

@@ -135,13 +185,19 @@ export default function VerificationCodeControl( {
* So here we await the `digits` is reset back to `initDigits` by above useEffect and sync to internal value,
Copy link
Member

Choose a reason for hiding this comment

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

💅
The code comments are outdated after changing the position and implementation of the original useEffect.

Comment on lines +151 to +157
// edge case: blur when pasting on last item
if (
newDigits.length === 1 &&
newDigits[ 0 ] !== digits[ idx ] &&
idx === DIGIT_LENGTH - 1
) {
e.target.blur();
Copy link
Member

Choose a reason for hiding this comment

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

📜
I think we don't really need this. I will propose an alternative that simplifies overall implementations by the new submitting code review.

@puntope puntope added status: on hold The issue is currently not prioritized. priority: low The issue/PR is low priority—not many people are affected or there’s a workaround, etc. labels Feb 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
priority: low The issue/PR is low priority—not many people are affected or there’s a workaround, etc. status: on hold The issue is currently not prioritized.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support pasting the phone verification code.
3 participants