Skip to content

Commit 92afcaa

Browse files
Add React 19 hooks
1 parent be0133c commit 92afcaa

File tree

8 files changed

+769
-266
lines changed

8 files changed

+769
-266
lines changed

README.md

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@
22

33
`react-basic-hooks` is a React hook API for [react-basic](https://github.com/lumihq/purescript-react-basic).
44

5-
_Note:_ This API relies on React `>=16.8.0`. For more info on hooks, see [React's documentation](https://reactjs.org/docs/hooks-intro.html).
5+
_Note:_ This library supports React `>=16.8.0` with full React 19 support. For more info on hooks, see [React's documentation](https://react.dev/reference/react).
66

7-
I recommend using PureScript's "qualified do" syntax while using this library (it's used in the examples, the `React.do` bits).
7+
I recommend using PureScript's "qualified do" syntax whilst using this library (it's used in the examples, the `React.do` bits).
88
It became available in the `0.12.2` compiler release.
99

1010
This library provides the `React.Basic.Hooks` module, which can completely replace the `React.Basic` module.
1111
It borrows a few types from the current `React.Basic` module like `ReactComponent` and `JSX` to make it easy to use both versions in the same project.
1212
If we prefer this API over the existing react-basic API, we may eventually replace `React.Basic` with this implementation.
1313

14+
## React Version Support
15+
16+
- **React 16.8+**: Core hooks (useState, useEffect, useReducer, useRef, useContext, useMemo, useDebugValue, useLayoutEffect)
17+
- **React 18+**: useId, useTransition, useDeferredValue, useSyncExternalStore, useInsertionEffect
18+
- **React 19+**: useOptimistic, useActionState, useEffectEvent (experimental)
19+
1420
## Example
1521

1622
```purs
@@ -27,3 +33,140 @@ mkCounter = do
2733
[ R.text $ "Increment: " <> show counter ]
2834
}
2935
```
36+
37+
## React 19 Hooks
38+
39+
### useOptimistic
40+
41+
Optimistically update the UI whilst waiting for an async action to complete. The optimistic state automatically reverts to the actual state when the action finishes.
42+
43+
```purs
44+
mkMessageList :: Component Props
45+
mkMessageList = do
46+
component "MessageList" \{ messages } -> React.do
47+
optimisticMessages /\ addOptimisticMessage <- useOptimistic messages \state newMessage ->
48+
Array.snoc state newMessage
49+
50+
isPending /\ startTransition <- useTransition
51+
52+
let handleSend message = startTransition do
53+
addOptimisticMessage message
54+
-- Async operation to send message to server
55+
sendToServer message
56+
57+
pure $ R.div_ (map renderMessage optimisticMessages)
58+
```
59+
60+
### useActionState
61+
62+
Manage form actions with built-in pending state. The action function receives the previous state and form data, making it ideal for form submissions. Uses Effect for synchronous operations.
63+
64+
```purs
65+
mkForm :: Component Unit
66+
mkForm = do
67+
component "Form" \_ -> React.do
68+
state /\ (formAction /\ isPending) <- useActionState initialState updateFn
69+
where
70+
updateFn prevState formData = do
71+
-- Process form submission (Effect version)
72+
result <- submitToServer formData
73+
pure (Result.fromEither result)
74+
75+
pure $ R.button
76+
{ disabled: isPending
77+
, onClick: handler_ (formAction myFormData)
78+
, children: [ R.text if isPending then "Submitting..." else "Submit" ]
79+
}
80+
```
81+
82+
For progressive enhancement (form works without JavaScript), use `useActionStateWithPermalink`:
83+
84+
```purs
85+
state /\ (formAction /\ isPending) <- useActionStateWithPermalink initialState updateFn "/api/submit"
86+
87+
pure $ R.form
88+
{ action: formAction -- Falls back to /api/submit without JS
89+
, children: [ ... ]
90+
}
91+
```
92+
93+
### useAffActionState
94+
95+
Aff version of `useActionState` for async operations. Available in `React.Basic.Hooks.Aff`. Uses Aff for natural async handling.
96+
97+
```purs
98+
import React.Basic.Hooks.Aff (useAffActionState)
99+
100+
mkForm :: Component Unit
101+
mkForm = do
102+
component "Form" \_ -> React.do
103+
state /\ (formAction /\ isPending) <- useAffActionState initialState affFn
104+
where
105+
affFn prevState formData = do
106+
-- Process form submission (Aff version - natural async!)
107+
result <- Aff.submitToServer formData
108+
pure (Result.fromEither result)
109+
110+
pure $ R.button
111+
{ disabled: isPending
112+
, onClick: handler_ (formAction myFormData)
113+
, children: [ R.text if isPending then "Submitting..." else "Submit" ]
114+
}
115+
```
116+
117+
With permalink: `useAffActionStateWithPermalink initialState affFn "/api/submit"`
118+
119+
### useEffectEvent
120+
121+
Extract non-reactive logic from Effects. The returned function can access the latest props and state without causing the Effect to re-run when those values change.
122+
123+
```purs
124+
mkComponent :: Component Props
125+
mkComponent = do
126+
component "Component" \{ url, onSuccess } -> React.do
127+
count /\ setCount <- useState 0
128+
129+
-- onSuccess can use the latest count without re-running the effect
130+
onSuccessEvent <- useEffectEvent \data -> do
131+
onSuccess data count
132+
133+
-- Effect only re-runs when url changes, not when count changes
134+
useEffect url do
135+
response <- fetchData url
136+
onSuccessEvent response
137+
pure mempty
138+
139+
pure $ R.div_ [ ... ]
140+
```
141+
142+
## Available Hooks
143+
144+
### Core Hooks (React 16.8+)
145+
- `useState` / `useState'` — State management
146+
- `useEffect` / `useEffectOnce` / `useEffectAlways` — Side effects
147+
- `useLayoutEffect` — Synchronous layout effects
148+
- `useReducer` — State management with reducers
149+
- `useRef` — Mutable refs
150+
- `useContext` — Context consumption
151+
- `useMemo` — Memoised computation
152+
- `useDebugValue` — DevTools debugging labels
153+
154+
### React 18 Hooks
155+
- `useId` — Unique ID generation
156+
- `useTransition` — Concurrent transitions
157+
- `useDeferredValue` — Deferred value updates
158+
- `useSyncExternalStore` — External store synchronisation
159+
- `useInsertionEffect` — DOM mutation effects
160+
161+
### React 19 Hooks
162+
- `useOptimistic` — Optimistic UI updates
163+
- `useActionState` — Form action management
164+
- `useEffectEvent` — Non-reactive effect logic (experimental)
165+
166+
### Additional Features
167+
- `memo` / `memo'` — Component memoisation
168+
- `component` — Component creation
169+
- Custom hooks via `React.Basic.Hooks.Aff` for async effects
170+
- `React.Basic.Hooks.Suspense` for Suspense support
171+
- `React.Basic.Hooks.ErrorBoundary` for error boundaries
172+
```

0 commit comments

Comments
 (0)