Executing effects in client components

Since Geolocation is a client-only service, we provide it to RuntimeClient.

We add Geolocation.layer to MainLayer inside RuntimeClient:

src/services/RuntimeClient.ts
import { Geolocation } from "@effect/platform-browser";
import { Layer, ManagedRuntime } from "effect";

const MainLayer = Layer.mergeAll(Geolocation.layer);

export const RuntimeClient = ManagedRuntime.make(MainLayer);

Most services in @effect/platform-browser and other platform libraries already provide a valid layer implementation, like here with Geolocation.layer.

With this we can now use RuntimeClient to execute action, since RuntimeClient includes a valid implementation of Geolocation.

Executing effects in client components

We can use useActionState again to execute action inside Location:

useActionState is not limited to server actions. It can be used to execute any asynchronous function.

src/components/Location.tsx
export default function Location() {
  const [location, getLocation, pending] = useActionState(
    () => RuntimeClient.runPromise(action),
    null
  );
  return (
    <p>
      Location:{" "}
      {location !== null
        ? `${location.coords.latitude} ${location.coords.longitude}`
        : "-"}
    </p>
  );
}

We then also add a button to trigger getLocation (so we can avoid useEffect 😅):

src/components/Location.tsx
export default function Location() {
  const [location, getLocation, pending] = useActionState(
    () => RuntimeClient.runPromise(action),
    null
  );
  return (
    <div>
      <button disabled={pending} onClick={getLocation}>
        Get Location
      </button>
      <p>
        Location:{" "}
        {location !== null
          ? `${location.coords.latitude} ${location.coords.longitude}`
          : "-"}
      </p>
    </div>
  );
}

Let's not forget to add Location to index.tsx:

src/pages/index.tsx
export default async function HomePage() {
  return (
    <div>
      <title>Index</title>
      <Location />
      {await RuntimeServer.runPromise(
        main.pipe(
          Effect.match({
            onFailure: Match.valueTags({
              ParseError: (error) => <span>ParseError</span>,
              RequestError: (error) => <span>RequestError</span>,
              ResponseError: (error) => <span>ResponseError</span>,
            }),
            onSuccess: (posts) => <Posts posts={posts} />,
          })
        )
      )}
    </div>
  );
}

Clicking "Get Location" will now trigger getLocation. Your browser will ask you for permission to access your location. When you click "Allow", the location will be displayed.

You can now appreciate the advantage of having a server and client runtimes. Each runtime contains services specific for the two environments.

This avoids most problems when handling the separation between server and client. It becomes impossible at compile time to execute client effects on the server, and vice versa.