| name | fp-react |
| description | Practical patterns for using fp-ts with React - hooks, state, forms, data fetching. Works with React 18/19, Next.js 14/15. |
| category | Development & Code Tools |
| source | antigravity |
| tags | ["typescript","react","node","api","ai","seo"] |
| url | https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/fp-react |
Functional Programming in React
Practical patterns for React apps. No jargon, just code that works.
Quick Reference
| Pattern | Use When |
|---|
Option | Value might be missing (user not loaded yet) |
Either | Operation might fail (form validation) |
TaskEither | Async operation might fail (API calls) |
RemoteData | Need to show loading/error/success states |
pipe | Chaining multiple transformations |
1. State with Option (Maybe It's There, Maybe Not)
Use Option instead of null | undefined for clearer intent.
Basic Pattern
import { useState } from 'react'
import * as O from 'fp-ts/Option'
import { pipe } from 'fp-ts/function'
interface User {
id: string
name: string
email: string
}
function UserProfile() {
const [user, setUser] = useState<O.Option<User>>(O.none)
const handleLogin = (userData: User) => {
setUser(O.some(userData))
}
const handleLogout = () => {
setUser(O.none)
}
return pipe(
user,
O.match(
() => <button onClick={() => handleLogin({ id: '1', name: 'Alice', email: 'alice@example.com' })}>
Log In
</button>,
() => (
)
)
)
}
Chaining Optional Values
import * as O from 'fp-ts/Option'
import { pipe } from 'fp-ts/function'
interface Profile {
user: O.Option<{
name: string
settings: O.Option<{
theme: string
}>
}>
}
function getTheme(profile: Profile): string {
return pipe(
profile.user,
O.flatMap(u => u.settings),
O.map(s => s.theme),
O.getOrElse(() => 'light')
)
}
2. Form Validation with Either
Either is perfect for validation: Left = errors, Right = valid data.
Simple Form Validation
import * as E from 'fp-ts/Either'
import * as A from 'fp-ts/Array'
import { pipe } from 'fp-ts/function'
const validateEmail = (email: string): E.Either<string, string> =>
email.includes('@')
? E.right(email)
: E.left('Invalid email address')
const validatePassword = (password: string): E.Either<string, string> =>
password.length >= 8
? E.right(password)
: E.left('Password must be at least 8 characters')
const validateName = (name: string): E.Either<string, string> =>
name.trim().length > 0
? E.right(name.trim())
: E.left('Name is required')
Collecting All Errors (Not Just First One)
import * as E from 'fp-ts/Either'
import { sequenceS } from 'fp-ts/Apply'
import { getSemigroup } from 'fp-ts/NonEmptyArray'
import { pipe } from 'fp-ts/function'
const validateAll = sequenceS(E.getApplicativeValidation(getSemigroup<string>()))
interface SignupForm {
name: string
email: string
password: string
}
interface ValidatedForm {
name: string
email: string
password: string
}
function validateForm(form: SignupForm): E.Either<string[], ValidatedForm> {
return pipe(
validateAll({
name: pipe(validateName(form.name), E.mapLeft(e => [e])),
: ((form.), E.( [e])),
: ((form.), E.( [e])),
})
)
}
() {
[form, setForm] = ({ : , : , : })
[errors, setErrors] = useState<[]>([])
= () => {
(
(form),
E.(
(errs),
{
([])
(valid)
}
)
)
}
(
)
}
Field-Level Errors (Better UX)
type FieldErrors = Partial<Record<keyof SignupForm, string>>
function validateFormWithFieldErrors(form: SignupForm): E.Either<FieldErrors, ValidatedForm> {
const errors: FieldErrors = {}
pipe(validateN