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

[BUG] Fixed isLoading logic in EuiConfirmModal docs #7026

Merged
merged 9 commits into from
Aug 7, 2023

Conversation

1Copenut
Copy link
Contributor

@1Copenut 1Copenut commented Aug 1, 2023

Summary

Fixed the confirmation modal where users are asked to type delete into an input to enable the red confirm delete button. Closes #7013.

QA

Remove or strikethrough items that do not apply to your PR.

General checklist

  • Checked in both light and dark modes
  • Checked in mobile
  • Checked in Chrome, Safari, Edge, and Firefox
  • Checked Code Sandbox works for any docs examples

Screen Shot 2023-08-01 at 3 44 01 PM
Screen Shot 2023-08-01 at 3 44 08 PM

* Updated isLoading prop to check for input string value.
@1Copenut 1Copenut added bug documentation Issues or PRs that only affect documentation - will not need changelog entries skip-changelog labels Aug 1, 2023
@1Copenut 1Copenut self-assigned this Aug 1, 2023
@@ -49,12 +49,12 @@ export default () => {
buttonColor="danger"
initialFocus="[name=delete]"
confirmButtonDisabled={value.toLowerCase() !== 'delete'}
isLoading={isLoading}
isLoading={value.toLowerCase() !== 'delete' ?? isLoading}
Copy link
Member

@cee-chen cee-chen Aug 1, 2023

Choose a reason for hiding this comment

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

this is a bandaid fix, it doesn't address the root issue above as to why setIsLoading(false) up above does not correctly work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll take another run at it, and focus on the setIsLoading logic.

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_7026/

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_7026_buildkite/

@1Copenut 1Copenut requested a review from cee-chen August 2, 2023 15:04
@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_7026_buildkite/

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_7026/

Comment on lines 13 to 19
const searchTimeout = setTimeout(() => {
// Simulate a remotely-executed search.
setIsLoading(false);
}, 1200);

clearTimeout(searchTimeout);
useEffect(() => {
if (value === 'delete') {
setIsLoading(false);
}
}, [value]);
Copy link
Member

@cee-chen cee-chen Aug 2, 2023

Choose a reason for hiding this comment

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

Trevor, I'm not sure you're understanding the intention of the demo and its code. :)

Take a look at the demo's description to help understand what the goal of this demo should be. Particularly: "This is helpful to indicate the fetching of data".

With that in mind, it becomes clear that the original setTimeout() was mocking an production async API call that takes 1.2 seconds to complete.

When writing code for demos/examples, we need to be conscious of "does this make sense as a scenario that a consumer might run into? And if so, how can we demonstrate to them the right UX to apply?" In this case, the UX you've written does not make sense. We don't want to represent a loading state when a user hasn't entered in the correct text. A loading state only makes sense for async data that isn't yet available.

Per the demo description again: "wait for a user's input before enabling the confirm action". We want to represent a disabled state when delete has not yet been typed. This logic was already being handled by the previous code:

confirmButtonDisabled={value.toLowerCase() !== 'delete'}

So again, let's take this all the way from the top. The setTimeout is not the issue, it's the fact that it's never actually getting called.

@1Copenut 1Copenut requested a review from cee-chen August 4, 2023 17:13
@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_7026_buildkite/

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_7026/

const closeModal = () => {
setIsModalVisible(false);
setIsLoading(false);
clearTimeout(searchTimeout);
setValue('');
Copy link
Member

Choose a reason for hiding this comment

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

Great catch on this - I really like this addition!

Comment on lines +18 to +21
setTimeout(() => {
// Simulate a remotely-executed search.
setIsLoading(false);
}, 1200);
Copy link
Member

@cee-chen cee-chen Aug 4, 2023

Choose a reason for hiding this comment

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

I'd be curious to hear your rationale for inlining the setTimeout vs leaving searchTimeout and the clearTimeouts in place. I don't strongly object to it/wouldn't consider it a blocker, but I'd like to hear the reasoning for changing it from the previous code.

I will note it does lead to potential race conditions: if a user opens the modal and then quickly closes it and re-opens it - the setTimeout will still be running in the background and the modal will render on 2nd open as loaded as opposed to loading.

The reason I wouldn't consider this a blocker is because this is a pretty basic demo and doesn't necessarily need to account for such edge cases. However, it seems odd to go from accounting for that behavior in code to no longer accounting for that behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've been refactoring it after thinking more about. I added a useEffect and this is the code I'm working with locally that properly clears the timeout:

const [isLoading, setIsLoading] = useState(false);
const [isModalVisible, setIsModalVisible] = useState(false);
const [value, setValue] = useState('');

const showModal = () => {
  setIsModalVisible(true);
  setIsLoading(true);
};

const closeModal = () => {
  setIsModalVisible(false);
  setIsLoading(false);
  setValue('');
};

useEffect(() => {
  const searchTimeout = setTimeout(() => {
    // Simulate a remotely-executed search.
    setIsLoading(false);
  }, 1200);

  return () => {
    clearTimeout(searchTimeout);
  };
}, [isModalVisible]);

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setValue(e.target.value);
};

...

Copy link
Member

@cee-chen cee-chen Aug 4, 2023

Choose a reason for hiding this comment

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

Can you clarify what exactly about the clearTimeout wasn't working? I'm not understanding why simply adding searchTimeout() to the old openModal call wasn't working, and why all this extra logic is needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure thing. When I loaded the docs site, searchTimeout wouldn't run if I tried to invoke it from inside showModal(). VSCode gave me an error

This expression is not callable.
  Type 'Timeout' has no call signatures.ts(2349)
Screen Shot 2023-08-04 at 2 24 39 PM

Copy link
Member

@cee-chen cee-chen Aug 4, 2023

Choose a reason for hiding this comment

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

Ha I did totally miss the part where searchTimeout isn't actually a function, apologies.

Why not make it as such? Would that not simplify/negate having to use a useEffect? e.g.

let timeoutId;
const searchTimeout = () => setTimeout(() => setIsLoading(false), 1200));

const openModal = () => {
  // ...
  timeoutId = searchTimeout();
});

const closeModal = () => {
  // ...
  clearTimeout(timeoutId);
});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I rewrote the solution to use this suggestion. I was having a time figuring out how to declare, then call the setTimeout(). Your suggestion here makes the logic simpler and removes the two run useEffect() which is much cleaner.

@1Copenut 1Copenut requested a review from cee-chen August 7, 2023 14:27
@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_7026_buildkite/

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_7026/

}, 1200);

clearTimeout(searchTimeout);
let timeOutId: ReturnType<typeof setTimeout>;
Copy link
Member

Choose a reason for hiding this comment

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

[not a change request, just a nit] I personally would stick to the casing of the existing API, e.g. timeoutId since setTimeout does not capitalize the out

Copy link
Member

@cee-chen cee-chen left a comment

Choose a reason for hiding this comment

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

🚢

Copy link
Member

@cee-chen cee-chen left a comment

Choose a reason for hiding this comment

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

Wait, sorry, your timeout name changes reverted re-adding the comment.

@1Copenut 1Copenut requested a review from cee-chen August 7, 2023 15:29
Copy link
Member

@cee-chen cee-chen left a comment

Choose a reason for hiding this comment

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

To be clear, I wasn't asking for the comment to be restored in exactly the format it was before - above the const searchTimeout was also fine. It just needed to still be there :)

Not a big deal either way, let's get this in

@1Copenut 1Copenut enabled auto-merge (squash) August 7, 2023 15:35
@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_7026_buildkite/

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_7026/

@elasticmachine
Copy link
Collaborator

💚 Build Succeeded

History

cc @1Copenut

@1Copenut 1Copenut merged commit e264226 into elastic:main Aug 7, 2023
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug documentation Issues or PRs that only affect documentation - will not need changelog entries skip-changelog
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[docs][EuiConfirmModal] Example is broken in production
4 participants