A common practice is to add custom methods to services to simplify common tasks.
For example, we can add a get static method that uses Effect.fromNullable to make sure that a form value is present:
import { Context, Effect } from "effect";
export class FormData extends Context.Tag("FormData")<
FormData,
globalThis.FormData
>() {
static readonly get = (name: string) =>
this.pipe(
Effect.flatMap(
(formData) => Effect.fromNullable(
// 👇 `formData` is the actual `FormData` web API inside the service
formData.get(name)?.toString()
)
)
);
}get returns Effect<string, NoSuchElementException, FormData>:
string: the value of the form with the givennameif presentNoSuchElementException: error when the form doesn't contain thenamevalueFormData: dependency on theFormDataservice
We can now use FormData.get inside Form:
export default function Form() {
const [_, action] = useActionState(
() =>
RuntimeClient.runPromise(
Effect.gen(function* () {
const username = yield* FormData.get("username");
})
),
null
);
return (
<form action={action}>
<input type="text" name="username" />
<button>Submit</button>
</form>
);
}You could then send username (or any other form value) to an API endpoint (which may be implemented using HttpClient and composed as a service).
In this example we simply log the value to the console (using the Console service provided by effect):
export default function Form() {
const [_, action] = useActionState(
() =>
RuntimeClient.runPromise(
Effect.gen(function* () {
const username = yield* FormData.get("username");
yield* Console.log(username);
})
),
null
);
return (
<form action={action}>
<input type="text" name="username" />
<button>Submit</button>
</form>
);
}