The last step is actually providing the dependency on FormData
.
useActionState
gives access to FromData
as the second argument of the action function. We extract it and use Effect.provideService
to provide it to the effect:
export default function Form() {
const [_, action] = useActionState(
(_: unknown, payload: globalThis.FormData) =>
RuntimeClient.runPromise(
Effect.gen(function* () {
const username = yield* FormData.get("username");
yield* Console.log(username);
}).pipe(
Effect.provideService(FormData, payload),
)
),
null
);
return (
<form action={action}>
<input type="text" name="username" />
<button>Submit</button>
</form>
);
}
Finally, let's add Form
to index.tsx
:
export default async function HomePage() {
return (
<div>
<title>Index</title>
<Location />
<Form />
{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>
);
}
This is it. When the form is submitted, the effect will be executed and username
will be logged to the console.
This example aims to show the benefits of the composition model of effect (service and layers). You can implement effects that are independent of concrete values, and only at the end provide them.
In fact, you can also extract the effect from the component and implement it in isolation. Then inside the component you only call Effect.provideService
to provide the value:
// 👇 `program` is independent from the actual component
const program = Effect.gen(function* () {
const username = yield* FormData.get("username");
yield* Console.log(username);
});
export default function Form() {
const [_, action] = useActionState(
(_: unknown, payload: globalThis.FormData) =>
RuntimeClient.runPromise(
program.pipe(Effect.provideService(FormData, payload))
),
null
);
return (
<form action={action}>
<input type="text" name="username" />
<button>Submit</button>
</form>
);
}
This composition model is a type-safe and convenient alternative to passing values as function parameters:
const program = (formData: globalThis.FormData) =>
Effect.gen(function* () {
const username = yield* FormData.get("username");
yield* Console.log(username);
}).pipe(Effect.provideService(FormData, formData));
export default function Form() {
const [_, action] = useActionState(
(_: unknown, payload: globalThis.FormData) =>
RuntimeClient.runPromise(program(payload)),
null
);
return (
<form action={action}>
<input type="text" name="username" />
<button>Submit</button>
</form>
);
}
This is extremely useful when working with complex values and services. Imagine having to pass countless function parameters and keep track of their types.
With effect, you can compose services and layers to create even complex effects that are easy to understand and maintain.