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 givenname
if presentNoSuchElementException
: error when the form doesn't contain thename
valueFormData
: dependency on theFormData
service
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>
);
}