solid-primitives icon indicating copy to clipboard operation
solid-primitives copied to clipboard

useUrl

Open huseeiin opened this issue 10 months ago • 12 comments

Describe The Problem To Be Solved

on the server getRequestEvent() has Request.url but its not a convenient URL object, and on the client we have location which is not exactly the same as URL

Suggest A Solution

import { getRequestEvent, isServer } from "solid-js/web";

function useUrl() {
  return new URL(isServer ? getRequestEvent()!.request.url : location.href);
}

should this be added? just a suggestion!

huseeiin avatar Mar 10 '25 18:03 huseeiin

How about making it reactive in the client (on history change event)?

atk avatar Mar 11 '25 06:03 atk

How about making it reactive in the client (on history change event)?

awesome idea!

huseeiin avatar Mar 11 '25 06:03 huseeiin

But in a composable way, so keep useUrl and add createUrl.

atk avatar Mar 11 '25 07:03 atk

But in a composable way, so keep useUrl and add createUrl.

But in a composable way, so keep useUrl and add createUrl.

function useUrl() {
  const [currentUrl, setCurrentUrl] = createSignal(
    new URL(isServer ? getRequestEvent()!.request.url : location.href)
  );

  createEffect(() => {
    const handleLocationChange = () => {
      setCurrentUrl(new URL(location.href));
    };

    addEventListener("popstate", handleLocationChange);
    addEventListener("hashchange", handleLocationChange);

    return () => {
      removeEventListener("popstate", handleLocationChange);
      removeEventListener("hashchange", handleLocationChange);
    };
  });

  return currentUrl;
}

updated code with help from claude. you can add anything else you want.

huseeiin avatar Mar 11 '25 07:03 huseeiin

Claude still treats Solid as if it was React. Instead of returning the unsubscriber, you need to wrap it in onCleanup.

atk avatar Mar 11 '25 08:03 atk

Claude still treats Solid as if it was React. Instead of returning the unsubscriber, you need to wrap it in onCleanup.

createEffect(() => {
  const handleLocationChange = () => {
    setCurrentUrl(new URL(location.href));
  };

  addEventListener("popstate", handleLocationChange);
  addEventListener("hashchange", handleLocationChange);

  onCleanup(() => {
    removeEventListener("popstate", handleLocationChange);
    removeEventListener("hashchange", handleLocationChange);
  });
});

is this correct?

huseeiin avatar Mar 11 '25 08:03 huseeiin

we don't need an effect here, should i just change it to an onMount?

huseeiin avatar Mar 11 '25 08:03 huseeiin

Since there are no reactive properties inside the effect, it is actually the same, but onMount is shorter, so go for it.

atk avatar Mar 11 '25 09:03 atk

Since there are no reactive properties inside the effect, it is actually the same, but onMount is shorter, so go for it.

ok can you create createUrl? i don't know what is that, and i see you use createHydratableSingletonRoot instead of normal createSignal what else do i need to make this a real primitive?

huseeiin avatar Mar 11 '25 09:03 huseeiin

createHydratableSignal is only meant for signals that are supposed to have different initial values on the server and on the client during hydration. Since our signal should basically have the same initial value, it is not necessary here.

In addition, we should use a equals function in the signal to avoid triggering updates when the URL itself is the same, only wrapped in a new object.

function useUrl() {
  return new URL(isServer ? getRequestEvent()!.request.url : location.href);
}

function createUrl() {
  const [currentUrl, setCurrentUrl] = createSignal(
    useUrl(),
    { equals: (a, b) => a.toString() === b.toString() }
  );

  onMount(() => {
    const handleLocationChange = () => {
      setCurrentUrl(new URL(location.href));
    };

    addEventListener("popstate", handleLocationChange);
    addEventListener("hashchange", handleLocationChange);

    onCleanup(() => {
      removeEventListener("popstate", handleLocationChange);
      removeEventListener("hashchange", handleLocationChange);
    });
  });

  return currentUrl;
}

atk avatar Mar 11 '25 10:03 atk

createHydratableSignal is only meant for signals that are supposed to have different initial values on the server and on the client during hydration. Since our signal should basically have the same initial value, it is not necessary here.

In addition, we should use a equals function in the signal to avoid triggering updates when the URL itself is the same, only wrapped in a new object.

function useUrl() { return new URL(isServer ? getRequestEvent()!.request.url : location.href); }

function createUrl() { const [currentUrl, setCurrentUrl] = createSignal( useUrl(), { equals: (a, b) => a.toString() === b.toString() } );

onMount(() => { const handleLocationChange = () => { setCurrentUrl(new URL(location.href)); };

addEventListener("popstate", handleLocationChange);
addEventListener("hashchange", handleLocationChange);

onCleanup(() => {
  removeEventListener("popstate", handleLocationChange);
  removeEventListener("hashchange", handleLocationChange);
});

});

return currentUrl; }

sounds awesome! is that all?

huseeiin avatar Mar 11 '25 10:03 huseeiin

small detail: should we call it createUrl/useUrl or createURL/useURL 🤔

huseeiin avatar Mar 11 '25 10:03 huseeiin