Migration from v7 to v8
Version 8 introduces significant changes to the validation system, improves type safety for metadata, and fixes next/navigation behaviors.
Legend:
- ⚠️ Breaking change
- 🆕 New feature
- ✨ Improvement
- 🔄 Refactor
What's new?
⚠️🆕 Standard Schema support
The biggest change in v8 is the switch to Standard Schema for validation. This removes the need for internal custom validation adapters and simplifies the API. You can find the supported Standard Schema libraries here.
import { createSafeActionClient } from "next-safe-action";
import { valibotAdapter } from "next-safe-action/adapters/valibot";
export const actionClient = createSafeActionClient({
validationAdapter: valibotAdapter(),
});
import { createSafeActionClient } from "next-safe-action";
export const actionClient = createSafeActionClient();
⚠️🆕 Navigation status and callbacks
The behavior when using functions from next/navigation
was very unclear and confusing in v7 and below, since all functions from next/navigation
produced a hasSucceeded
status and triggered onSuccess
callbacks.
This behavior has been changed in v8. Now, when you're using functions imported from next/navigation
in an action:
- the hooks
status
value will be"hasNavigated"
instead of"hasSucceeded"
; - a new
onNavigation()
callback will be triggered, both for actions and hooks, instead ofonSuccess()
. This callback receives anavigationKind
value, that indicates the type of navigation that occurred; - the
success
property of the middleware result will now befalse
, instead oftrue
, if a navigation function was called in a middleware function or in the action's server code function.
import { useAction } from "next-safe-action/hooks";
import { redirect } from "next/navigation";
// In the action definition
const action = actionClient.action(
async () => {
redirect("/");
},
{
onNavigation: async ({ navigationKind }) => {
// Do something with the navigation...
},
}
);
// In the component
const { execute, status } = useAction(action, {
onNavigation: ({ navigationKind }) => {
// Do something with the navigation...
},
});
⚠️✨ Stricter bound args validation
When using bound arguments with invalid data, errors are now thrown instead of being returned as part of the result object. This is because bound arguments should not be passed to the action from the user, instead they should be received from the server. So, this change aligns the behavior of bound arguments with the behavior of metadata and output validation, and prevents potential leakage of sensitive data on the client side.
const boundAction = action.bind(null, invalidBindArg);
// No error thrown on the server, `bindArgsValidationErrors`
// gets returned to the client in the result object.
const { bindArgsValidationErrors } = await boundAction(input);
const boundAction = action.bind(null, invalidBindArg);
// No bound arg errors are returned to the client, instead
// an `ActionBindArgsValidationError` is thrown on the server.
const result = await boundAction(input);
⚠️ Removal of deprecated executeOnMount
hook option
The deprecated executeOnMount
hook functionality has been removed in v8. Server Actions should be used only for mutations, so it doesn't make sense to execute them on mount. Or at least, it shouldn't be a common case and, above all, a library job. If you still need to do it, just use useEffect()
to trigger the execution, however you want.
✨ Type-checked metadata
This is a big improvement in type safety over v7. Metadata is now statically type-checked when passed to actions. So, now if you forget to pass the expected metadata shape, as defined by the defineMetadataSchema
init option, you will get a type error immediately:
✨ Custom thrown validation error messages
The throwValidationErrors
option now accepts both a boolean (just like in v7) and an object with a overrideErrorMessage()
function, that allows you to customize the thrown Error
message on the client side.
import { throwValidationErrors, overrideErrorMessage } from "next-safe-action";
const action = actionClient
.inputSchema(z.object({ name: z.string() }))
.action(
async () => {
return {
success: true,
};
},
{
throwValidationErrors: {
// If input validation fails, here we can customize the error message
// returned to the client.
overrideErrorMessage: async (validationErrors) => {
return validationErrors.name?._errors?.join(" ") ?? "";
},
},
}
);
✨ Added transitioning
status
The action status, when using hooks, now can be transitioning
as well. This value is set when the action has finished executing, but the transition is not yet complete. The transition state is managed by the useTransition()
React hook used under the hood in next-safe-action hooks.
🔄 Safe action result always defined
The action result object is now always defined. This allows you to destructure it without the need to check if it's defined or not first:
// Cannot access data directly, we need to check if
// result is defined first.
const result = await action(input);
if (result?.data) {
// Do something with the data...
}
// Now we can destructure the result object and
// access data directly.
const { data } = await action(input);
🔄 schema
method renamed to inputSchema
The library, since version 7.8.0, supports both input and output validation, respectively using the schema()
and outputSchema()
methods. In v8, the schema()
method has been renamed to inputSchema()
to better reflect its purpose, and avoid potential confusion.
The schema()
method is deprecated and will be removed in a future version, but it's still available for backward compatibility. It's now just an alias for inputSchema()
:
actionClient.schema(/* ... */)
actionClient.inputSchema(/* ... */)
To update your actions, you can just use the search and replace feature of your editor to replace all occurrences of .schema()
with .inputSchema()
.
🔄 Deprecation of useStateAction
hook
The useStateAction()
hook has been deprecated. It's always been kind of a hack to begin with, and it doesn't support progressive enhancement, since it tries to do what the useAction()
and useOptimisticAction()
hooks do, with JS functionality.
So, from now on, the recommended way to use stateful actions is to do it with the React's built in useActionState()
hook, as explained in this section of the documentation.
Requirements
next-safe-action version 8 requires Next.js 14 and React 18.2.0 or later to work.
What about v7?
You can find the v7 documentation here.