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: add useNetworkStatus #117

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

michal-weglarz
Copy link

Hi all 👋
I thought it might be useful to have this useNetworkStatus utility to get the current status of the network connection at any time. It could be used to inform users that they have a bad connection, or that they have temporarily lost access to the internet altogether. I also added the updatedAt property, which can be used, for example, to show how much time users were offline before coming back online (please, see the attached demo).

It's heavily inspired by useNetworkState from React-Hookz/web.

This hasn't been discussed in a separate feature request yet, so feel free to reject this PR if you think this utility doesn't belong in the library.

Demo in Chrome:

use-network-status-chrome-demo.mov

Demo in Safari/Firefox:

use-network-stastus-firefox-safari-demo.mov

The returned status always includes online and updatedAt.
Other properties are returned based on the NetworkInformation interface and depend on your browser's compatibility.

Copy link

changeset-bot bot commented Jul 11, 2024

⚠️ No Changeset found

Latest commit: baaff33

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

github-actions bot commented Jul 11, 2024

built with Refined Cloudflare Pages Action

⚡ Cloudflare Pages Deployment

Name Status Preview Last Commit
runed ✅ Ready (View Log) Visit Preview 14c85cb

chore: add .idea to gitignore

chore: clean up

chore: clean up
Copy link

@Not-Jayden Not-Jayden left a comment

Choose a reason for hiding this comment

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

Nice one!

Left some small nitpicks.

More broadly, I don't want to speak for the maintainers, but looking at the current naming/API patterns of the codebase my assumption is the use prefix is being reserved for functions that accept a callback to observe changes.

I'd probably either rework so it can be used like:

useNetworkStatus((data) => { 
  // do something with new data 
});

or keep it essentially the same as the current implementation, but change it to a NetworkStatus class (classes also seem to be the pattern for encapsulating reactive state in this repo).

const networkStatus = new NetworkStatus();

Second makes most sense to me, but you could potentially even have both if you really wanted. Hopefully maintainers can give guidance.

@github-actions github-actions bot requested a deployment to Preview July 12, 2024 16:20 Abandoned
refactor: refactor the way event listeners are removed on unmount
Copy link
Member

@huntabyte huntabyte left a comment

Choose a reason for hiding this comment

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

Thanks for taking the time to contribute this! Left some notes!

Copy link

@Not-Jayden Not-Jayden left a comment

Choose a reason for hiding this comment

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

Looking great! Just passing observations until maintainers can provide a proper review :)

sites/docs/content/utilities/network-status.md Outdated Show resolved Hide resolved
@huntabyte
Copy link
Member

Part of me wonders if we should just include a previous property and just provide that as an API out of the box. I'm also wondering if we should expose an isConnectionSupported property as well so users can better conditionally handle situations where none of the other properties will be defined.

What do you all think?

Great job with the implementation @michal-weglarz!

@TGlide
Copy link
Member

TGlide commented Jul 18, 2024

Why the previous property? Do you think it'd be common enough that its warranted over just using the Previous utility?

@huntabyte
Copy link
Member

huntabyte commented Jul 19, 2024

Why the previous property? Do you think it'd be common enough that its warranted over just using the Previous utility?

After thinking about it more, you're right. I think it might just create bloat that isn't always necessary, so it's best to leverage other utilities if necessary, as in the example. Disregard that one!

Copy link
Member

@TGlide TGlide left a comment

Choose a reason for hiding this comment

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

Awesome stuff! Some minor changes needed

);
#online: boolean = $state(false);
#updatedAt: Date = $state(new Date());
#downlink?: NetworkInformation["downlink"] = $state();
Copy link
Member

Choose a reason for hiding this comment

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

Can't these be deriveds that depend on connection?

Copy link
Member

Choose a reason for hiding this comment

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

Actually, can't we just use gets that use connection? Why do we need these individual fields?

Copy link
Author

Choose a reason for hiding this comment

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

Can we get the updated state without these? If connection or its properties, or online/offline status changes (e.g., random internet disconnection), how will the client be notified?

Copy link
Member

Choose a reason for hiding this comment

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

connection can be a state, since all of the other ones are just set from it. then updateStatus updates connection, and that's it

Copy link
Author

Choose a reason for hiding this comment

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

I pushed the commit where I simplified the state by depending solely on the derived connection. Seems like it's working exactly the same as before so definitely a great suggestion :)

sites/docs/content/utilities/network-status.md Outdated Show resolved Hide resolved
sites/docs/content/utilities/network-status.md Outdated Show resolved Hide resolved

```ts
const networkStatus = new NetworkStatus();
const previousNetworkStatus = new Previous(() => ({
Copy link
Member

Choose a reason for hiding this comment

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

tbh its a bit of a shame we can't spread here 🤔

Copy link
Member

Choose a reason for hiding this comment

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

@TGlide this is literally what made me consider doing the previous for the user for this one so they wouldn't have to do line by line. The only way I see being able to spread is exposing a $derived property on the class that is basically an object with all those props networkStatus.obj or networkStatus.all but idk how it feels or if it's worth the extra bloat tbh

Copy link
Member

Choose a reason for hiding this comment

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

Hmm tbh its probably fine. Most of these properties are just being proxied within the connection property, so you could just use that


<DemoContainer>
{#if networkStatus.isSupported}
<pre>{JSON.stringify(networkStatusCombined, null, 2)}</pre>
Copy link
Member

Choose a reason for hiding this comment

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

@huntabyte is it too hard to get syntax highlighting here?

Copy link
Member

Choose a reason for hiding this comment

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

@TGlide, uhh, we could hack it a bit, but without hacks, we'd need to ship Shiki to the browser.

Copy link
Member

Choose a reason for hiding this comment

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

No need then!

@michal-weglarz
Copy link
Author

michal-weglarz commented Aug 2, 2024

I've noticed an issue with updating the online field when disabling the network manually (e.g., turning off Wi-Fi on my laptop) rather than throttling the network in a browser. This can result in the value being stale and not reflecting the actual current network state. Interestingly, this problem occurs only in Chrome, not in Firefox or Safari.

We could use something like this to check again if the value is up to date:

	#updateStatus = () => {
		if (!this.#navigator) return;
		this.#online = this.#navigator.onLine;
		this.#updatedAt = new Date();

		setTimeout(() => {
			if (!this.#navigator) return;
			const newOnlineStatus = this.#navigator.onLine;
			if (this.#online !== newOnlineStatus) {
				this.#online = newOnlineStatus;
                                this.#updatedAt = new Date();
			}
		}, 100);
	};

From my manual testing, this approach indeed mitigates the problem described above. However, it seems a little brittle. Would you be fine with adding it to the code, or do you see any other potential fix?

EDIT.
What would also work in this case is this:

	constructor() {
		onMount(() => {
			this.#updateStatus();

			useEventListener(window, "online", this.#updateStatus, { passive: true });
			useEventListener(window, "offline", this.#updateStatus, { passive: true });

			if (this.#connection) {
				useEventListener(this.#connection, "change", this.#updateStatus, { passive: true });
			}
		});
	}

This would probably be a better solution, but the handling of updatedAt should be reworked as well. Otherwise, based on the example included in this PR, this would result in a notification like this: '(...). Catch up with what you missed during your 0 seconds offline.' The 0 occurs because the callback updating updatedAt is called too frequently.

React-hookz/web solves this by only storing the timestamp of the moment when the status changes to online/offline. This approach doesn't include timestamps of any other updates.

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.

4 participants