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

feat: PWA pull to refresh #1599

Merged
merged 4 commits into from
Nov 18, 2024
Merged

Conversation

Soxasora
Copy link
Contributor

@Soxasora Soxasora commented Nov 17, 2024

Description

Pull-to-refresh for iOS, completing #1258.

As router.reload() is not graceful and router.replace(...) adds "/~" to the path breaking functionality across the app, I've decided to use router.push() instead, reloading the same page.
This updates the browser's history but since we're talking about a PWA there's no history to be accessed from the user.

This component wraps <Layout /> and is present in every page that make use of it.

Screenshots

doc_2024-11-17_22-03-08.mp4

Additional Context

At the moment it's enabled also for Android, disabling its native pull-to-refresh. If we want to enable it back we can pass android=false

Not being a master of creativity and CSS I want to know if you'd like to see something else instead of refreshing..., even though I think it's geeky in its own way

Checklist

Are your changes backwards compatible? Please answer below:
checkPWA prevents it from being triggered on anything that's not a PWA, also being a new component that has minimal CSS and loads only if the user is at the top of the page, I can say that it doesn't break functionality.

On a scale of 1-10 how well and how have you QA'd this change and any features it might affect? Please answer below:
7, I don't have an Android phone and I would like to know if there are any problems with it, as said before we can disable or enable Android's PTR.
As it should work only in a PWA context, I also tested it on browsers

For frontend changes: Tested on mobile? Please answer below:
Yes, being a mobile-only feature

Did you introduce any new environment variables? If so, call them out explicitly here: No

@ekzyis ekzyis self-requested a review November 17, 2024 23:52
Copy link
Member

@ekzyis ekzyis left a comment

Choose a reason for hiding this comment

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

Hey, this is very impressive! It works great and the code is very clean. Stackers (myself included) are going to love this! Additionally, this was a breeze to review. Thank you so much for this! Looking forward to more contributions from you 👀

Now to the review:

I tested this on desktop by simply manually setting isPWA to true and then using the mobile emulator. I also tested that the refresh actually works by loading new comments that I created in a new tab.

I only have a few UX and CSS nitpicks:

  1. is it possible to prevent scrolling the window down when the pull to refresh shows up and I want to abort?
2024-11-18.01-26-46.mp4
  1. Why did you add the spacer during refresh? Afaict it does nothing except create layout shift. Therefore I think we should also remove it, it's even more cleaner that way. So the meme should have actually been this:

image

  1. the pull message is a little big so I think we should use font-size: small in the pullMessage CSS class.

  2. the distance to pull down might be a little too much (almost half of the screen) but we can roll this out and then see if we need to adjust it. actually, ignore this. I played around more and a big threshold as-is is better.

  3. pulling down also works when I start to pull down from the bottom of the screen when I am scrolled all the way to the top. Not sure if that's how other apps do it, I think you need to start at the top?

After dropping refreshing ..., the spacer and making the text smaller, the UI/UX is this:

2024-11-18.01-53-19.mp4

Regarding Android: I haven't tested it on Android but since this works so well, I think we can keep your code enabled on Android to have a consistent experience.

If anything is weird, it's very easy to go back to the native behavior on Android.

Since these are all just nitpicks and it's already as-is a huge improvement, this is approved from my side ✅

@huumn is the final boss though

Comment on lines 24 to 26
useEffect(() => {
checkPWA()
}, [])
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
useEffect(() => {
checkPWA()
}, [])
useEffect(checkPWA, [])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops 😳

}, [isPWA, isAndroid, android])

const handleTouchMove = useCallback((e) => {
if (touchStartY.current === 0) return
Copy link
Member

Choose a reason for hiding this comment

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

Is this needed so the text doesn't get stuck in some cases? At least that's what I can tell starts happening when I remove this line. A comment in the code would be nice 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.

Yes! I thought I was going crazy because between refreshes the text would get stuck, noted thanks :P

}, [])

const handleTouchEnd = useCallback(() => {
if (touchStartY.current === 0 || touchEndY.current === 0) return
Copy link
Member

Choose a reason for hiding this comment

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

Same reason here I suppose

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Touch can be finicky with JS, I wanted to make sure that we can't encounter weird behavior

import { useState, useRef, useEffect, useCallback } from 'react'
import styles from './pull-to-refresh.module.css'

const PULL_THRESHOLD = 300
Copy link
Member

@ekzyis ekzyis Nov 18, 2024

Choose a reason for hiding this comment

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

100 as twice the threshold when pull to refresh starts showing might be a better threshold., No, I played around with 100 and 300 is way better since it doesn't cause accidental refreshes.

I would also call this REFRESH_THRESHOLD since PULL_THRESHOLD sounds like that's when we show the first message.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Noted, you're right it would make much more sense!

{pullDistance > 0 || isRefreshing
? (
<>
<p className={`${styles.pullMessage} ${pullDistance > 50 || isRefreshing ? styles.fadeIn : ''}`}>
Copy link
Member

@ekzyis ekzyis Nov 18, 2024

Choose a reason for hiding this comment

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

This suggests you want to show the message when pullDistance > 50, but above you're checking for pullDistance > 0. Isn't checking it at one place enough? Using only pullDistance > 50 looks good to me.

Copy link
Contributor Author

@Soxasora Soxasora Nov 18, 2024

Choose a reason for hiding this comment

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

Ok so, I think that stayed there during multiple versions of this code, IIRC before placing touch checks this was a workaround to get me through refreshes and avoid unintended messages.
I'm going to test if that's still the case

<p className={`${styles.pullMessage} ${pullDistance > 50 || isRefreshing ? styles.fadeIn : ''}`}>
{getPullMessage()}
</p>
{isRefreshing && <div className={styles.spacer} />}
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 should also drop this since afaict, it only causes layout shift

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And it doesn't look clean

Comment on lines 22 to 25
.spacer {
position: relative;
margin-bottom: 32px;
}
Copy link
Member

Choose a reason for hiding this comment

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

see other comment

left: 50%;
top: -50px;
transform: translateX(-50%);
font-size: 18px;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
font-size: 18px;
font-size: small;

@ekzyis
Copy link
Member

ekzyis commented Nov 18, 2024

is it possible to prevent scrolling the window down when the pull to refresh shows up and I want to abort?

a solution for this might be to use the behavior in your first video where the whole page moves down when you pull down

@Soxasora
Copy link
Contributor Author

Soxasora commented Nov 18, 2024

Hey, this is very impressive! It works great and the code is very clean. Stackers (myself included) are going to love this! Additionally, this was a breeze to review. Thank you so much for this! Looking forward to more contributions from you 👀

Many many thanks! I'm glad that you like it!

is it possible to prevent scrolling the window down when the pull to refresh shows up and I want to abort?

a solution for this might be to use the behavior in your first video where the whole page moves down when you pull down

The behavior you're encountering should be uniquely present on desktop as it doesn't allow overscroll like mobile devices, if you notice in my two videos, the whole page moves down when you pull down :P

wait- now that I think about it, to make it theoretically work on Android I conditionally disable overscroll, we would get the same result you get on desktop... we definitely need to go with your UI/UX changes in order to make it pretty for both devices

  1. Why did you add the spacer during refresh? Afaict it does nothing except create layout shift. Therefore I think we should also remove it, it's even more cleaner that way. So the meme should have actually been this:

The spacer prevents the messages to appear on content, but since we're going towards a much cleaner experience I think it won't be a problem anymore (thanks!)

  1. the pull message is a little big so I think we should use font-size: small in the pullMessage CSS class.
  2. I played around more and a big threshold as-is is better.

I agree wholeheartedly to both, about the threshold, I tried with lots of values and 300 was the right number imo!

  1. pulling down also works when I start to pull down from the bottom of the screen when I am scrolled all the way to the top. Not sure if that's how other apps do it, I think you need to start at the top?

So as long as the user is at the top of the page, refresh can (or should) happen because you can't scroll higher, if the user is scrolling from the bottom but can't reach threshold it will make the user think that for scrolling he needs to start pulling from top.

After dropping refreshing ..., the spacer and making the text smaller, the UI/UX is this

You're right, I've been too conservative in terms of UI/UX

Regarding Android: I haven't tested it on Android but since this works so well, I think we can keep your code enabled on Android to have a consistent experience.

Thanks!!! Your changes will be beneficial to android too because of disabled overscroll :P

Since these are all just nitpicks and it's already as-is a huge improvement, this is approved from my side ✅

Nitpicks are what makes apps and sites feel good to use, thank you so much, will commit changes later in the day ❤️

@Soxasora
Copy link
Contributor Author

trim.5FAC5AE4-5EC9-4B63-B42A-E4C4A1454145.MOV

well it looks super cool.

I moved overscroll-behavior-y into the body to avoid double refreshes, applied changes and corrected some typos

@huumn huumn merged commit c3b7ad3 into stackernews:master Nov 18, 2024
6 checks passed
@Soxasora
Copy link
Contributor Author

i forgot to memoize the messages 🫣
with calculated margin we can stay safe across devices, didn't cross my mind, thanks @huumn!

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