Skip to content

Commit 79e48b0

Browse files
committedJun 13, 2024
fix(hooks): form actions support
1 parent c3a4b40 commit 79e48b0

File tree

1 file changed

+47
-46
lines changed

1 file changed

+47
-46
lines changed
 

‎packages/next-safe-action/src/hooks.ts

+47-46
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,6 @@ import type {
1616
} from "./hooks.types";
1717
import { isError } from "./utils";
1818

19-
/**
20-
* Default value for `result` object returned by `useAction`, `useOptimisticAction` and `useStateAction` hooks.
21-
*/
22-
export const EMPTY_HOOK_RESULT = {
23-
data: undefined,
24-
fetchError: undefined,
25-
serverError: undefined,
26-
validationErrors: undefined,
27-
} satisfies HookResult<any, any, any, any, any, any>;
28-
2919
const getActionStatus = <
3020
ServerError,
3121
S extends Schema | undefined,
@@ -138,7 +128,7 @@ export const useAction = <
138128
utils?: HookCallbacks<ServerError, S, BAS, CVE, CBAVE, Data>
139129
) => {
140130
const [, startTransition] = React.useTransition();
141-
const [result, setResult] = React.useState<HookResult<ServerError, S, BAS, CVE, CBAVE, Data>>(EMPTY_HOOK_RESULT);
131+
const [result, setResult] = React.useState<HookResult<ServerError, S, BAS, CVE, CBAVE, Data>>({});
142132
const [clientInput, setClientInput] = React.useState<S extends Schema ? InferIn<S> : void>();
143133
const [isExecuting, setIsExecuting] = React.useState(false);
144134
const [isIdle, setIsIdle] = React.useState(true);
@@ -147,13 +137,9 @@ export const useAction = <
147137

148138
const execute = React.useCallback(
149139
(input: S extends Schema ? InferIn<S> : void) => {
150-
setIsIdle(false);
151-
setClientInput(input);
152-
setIsExecuting(true);
153-
154-
return startTransition(() => {
155-
return safeActionFn(input as S extends Schema ? InferIn<S> : undefined)
156-
.then((res) => setResult(res ?? EMPTY_HOOK_RESULT))
140+
startTransition(() => {
141+
safeActionFn(input as S extends Schema ? InferIn<S> : undefined)
142+
.then((res) => setResult(res ?? {}))
157143
.catch((e) => {
158144
if (isRedirectError(e) || isNotFoundError(e)) {
159145
throw e;
@@ -165,25 +151,27 @@ export const useAction = <
165151
setIsExecuting(false);
166152
});
167153
});
154+
155+
ReactDOM.flushSync(() => {
156+
setIsIdle(false);
157+
setClientInput(input);
158+
setIsExecuting(true);
159+
});
168160
},
169161
[safeActionFn]
170162
);
171163

172164
const executeAsync = React.useCallback(
173165
(input: S extends Schema ? InferIn<S> : void) => {
174-
setIsIdle(false);
175-
setClientInput(input);
176-
setIsExecuting(true);
177-
178-
return new Promise<Awaited<ReturnType<typeof safeActionFn>>>((resolve, reject) => {
166+
const fn = new Promise<Awaited<ReturnType<typeof safeActionFn>>>((resolve, reject) => {
179167
startTransition(() => {
180168
safeActionFn(input as S extends Schema ? InferIn<S> : undefined)
181169
.then((res) => {
170+
setResult(res ?? {});
182171
resolve(res);
183172
})
184173
.catch((e) => {
185174
if (isRedirectError(e) || isNotFoundError(e)) {
186-
setResult(EMPTY_HOOK_RESULT);
187175
throw e;
188176
}
189177

@@ -195,18 +183,26 @@ export const useAction = <
195183
});
196184
});
197185
});
186+
187+
ReactDOM.flushSync(() => {
188+
setIsIdle(false);
189+
setClientInput(input);
190+
setIsExecuting(true);
191+
});
192+
193+
return fn;
198194
},
199195
[safeActionFn]
200196
);
201197

202198
const reset = () => {
203199
setIsIdle(true);
204200
setClientInput(undefined);
205-
setResult(EMPTY_HOOK_RESULT);
201+
setResult({});
206202
};
207203

208204
useActionCallbacks({
209-
result: result ?? EMPTY_HOOK_RESULT,
205+
result: result ?? {},
210206
input: clientInput as S extends Schema ? InferIn<S> : undefined,
211207
status,
212208
cb: utils,
@@ -246,7 +242,7 @@ export const useOptimisticAction = <
246242
} & HookCallbacks<ServerError, S, BAS, CVE, CBAVE, Data>
247243
) => {
248244
const [, startTransition] = React.useTransition();
249-
const [result, setResult] = React.useState<HookResult<ServerError, S, BAS, CVE, CBAVE, Data>>(EMPTY_HOOK_RESULT);
245+
const [result, setResult] = React.useState<HookResult<ServerError, S, BAS, CVE, CBAVE, Data>>({});
250246
const [clientInput, setClientInput] = React.useState<S extends Schema ? InferIn<S> : void>();
251247
const [isExecuting, setIsExecuting] = React.useState(false);
252248
const [isIdle, setIsIdle] = React.useState(true);
@@ -259,14 +255,10 @@ export const useOptimisticAction = <
259255

260256
const execute = React.useCallback(
261257
(input: S extends Schema ? InferIn<S> : void) => {
262-
setIsIdle(false);
263-
setClientInput(input);
264-
setIsExecuting(true);
265-
266-
return startTransition(() => {
258+
startTransition(() => {
267259
setOptimisticValue(input as S extends Schema ? InferIn<S> : undefined);
268-
return safeActionFn(input as S extends Schema ? InferIn<S> : undefined)
269-
.then((res) => setResult(res ?? EMPTY_HOOK_RESULT))
260+
safeActionFn(input as S extends Schema ? InferIn<S> : undefined)
261+
.then((res) => setResult(res ?? {}))
270262
.catch((e) => {
271263
if (isRedirectError(e) || isNotFoundError(e)) {
272264
throw e;
@@ -278,26 +270,28 @@ export const useOptimisticAction = <
278270
setIsExecuting(false);
279271
});
280272
});
273+
274+
ReactDOM.flushSync(() => {
275+
setIsIdle(false);
276+
setClientInput(input);
277+
setIsExecuting(true);
278+
});
281279
},
282280
[safeActionFn, setOptimisticValue]
283281
);
284282

285283
const executeAsync = React.useCallback(
286284
(input: S extends Schema ? InferIn<S> : void) => {
287-
setIsIdle(false);
288-
setClientInput(input);
289-
setIsExecuting(true);
290-
291-
return new Promise<Awaited<ReturnType<typeof safeActionFn>>>((resolve, reject) => {
285+
const fn = new Promise<Awaited<ReturnType<typeof safeActionFn>>>((resolve, reject) => {
292286
startTransition(() => {
293287
setOptimisticValue(input as S extends Schema ? InferIn<S> : undefined);
294288
safeActionFn(input as S extends Schema ? InferIn<S> : undefined)
295289
.then((res) => {
290+
setResult(res ?? {});
296291
resolve(res);
297292
})
298293
.catch((e) => {
299294
if (isRedirectError(e) || isNotFoundError(e)) {
300-
setResult(EMPTY_HOOK_RESULT);
301295
throw e;
302296
}
303297

@@ -309,18 +303,26 @@ export const useOptimisticAction = <
309303
});
310304
});
311305
});
306+
307+
ReactDOM.flushSync(() => {
308+
setIsIdle(false);
309+
setClientInput(input);
310+
setIsExecuting(true);
311+
});
312+
313+
return fn;
312314
},
313315
[safeActionFn, setOptimisticValue]
314316
);
315317

316318
const reset = () => {
317319
setIsIdle(true);
318320
setClientInput(undefined);
319-
setResult(EMPTY_HOOK_RESULT);
321+
setResult({});
320322
};
321323

322324
useActionCallbacks({
323-
result: result ?? EMPTY_HOOK_RESULT,
325+
result: result ?? {},
324326
input: clientInput as S extends Schema ? InferIn<S> : undefined,
325327
status,
326328
cb: {
@@ -366,22 +368,21 @@ export const useStateAction = <
366368
) => {
367369
const [result, dispatcher, isExecuting] = React.useActionState(
368370
safeActionFn,
369-
utils?.initResult ?? EMPTY_HOOK_RESULT,
371+
utils?.initResult ?? {},
370372
utils?.permalink
371373
);
372374
const [isIdle, setIsIdle] = React.useState(true);
373375
const [clientInput, setClientInput] = React.useState<S extends Schema ? InferIn<S> : void>();
374376
const status = getActionStatus<ServerError, S, BAS, CVE, CBAVE, Data>({
375377
isExecuting,
376-
result: result ?? EMPTY_HOOK_RESULT,
378+
result: result ?? {},
377379
isIdle,
378380
});
379381

380382
const execute = React.useCallback(
381383
(input: S extends Schema ? InferIn<S> : void) => {
382384
dispatcher(input as S extends Schema ? InferIn<S> : undefined);
383385

384-
// eslint-disable-next-line
385386
ReactDOM.flushSync(() => {
386387
setIsIdle(false);
387388
setClientInput(input);
@@ -391,7 +392,7 @@ export const useStateAction = <
391392
);
392393

393394
useActionCallbacks({
394-
result: result ?? EMPTY_HOOK_RESULT,
395+
result: result ?? {},
395396
input: clientInput as S extends Schema ? InferIn<S> : undefined,
396397
status,
397398
cb: {

0 commit comments

Comments
 (0)
Please sign in to comment.