|
| 1 | +--- |
| 2 | +sidebar_position: 4 |
| 3 | +description: Learn how to migrate from next-safe-action version 7 to version 8. |
| 4 | +sidebar_label: v7 to v8 |
| 5 | +--- |
| 6 | + |
| 7 | +# Migration from v7 to v8 |
| 8 | + |
| 9 | +Version 8 introduces significant changes to the validation system, improves type safety for metadata, and fixes next/navigation behaviors. |
| 10 | + |
| 11 | +## What's new? |
| 12 | + |
| 13 | +### [BREAKING] Standard Schema support |
| 14 | + |
| 15 | +The biggest change in v8 is the switch to [Standard Schema](https://github.com/standard-schema/standard-schema) for validation. This removes the need for external validation adapters and simplifies the API. You can find the supported libraries [here](https://github.com/standard-schema/standard-schema?tab=readme-ov-file#what-schema-libraries-implement-the-spec). |
| 16 | + |
| 17 | +```typescript title="v7 - using Valibot" |
| 18 | +import { createSafeActionClient } from "next-safe-action"; |
| 19 | +import { valibotAdapter } from "next-safe-action/adapters/valibot"; |
| 20 | + |
| 21 | +export const actionClient = createSafeActionClient({ |
| 22 | + validationAdapter: valibotAdapter(), |
| 23 | +}); |
| 24 | +``` |
| 25 | + |
| 26 | +```typescript title="v8 - using Standard Schema" |
| 27 | +import { createSafeActionClient } from "next-safe-action"; |
| 28 | + |
| 29 | +export const actionClient = createSafeActionClient(); |
| 30 | +``` |
| 31 | + |
| 32 | +### Type-checked metadata |
| 33 | + |
| 34 | +Metadata is now type-checked when passed to actions. So, now if you forget to pass the expected metadata, you will get a type error. |
| 35 | + |
| 36 | +<video controls autoPlay loop muted width="320" height="240"> |
| 37 | + <source src="/vid/metadata-v8.mp4"/> |
| 38 | +</video> |
| 39 | + |
| 40 | +### Custom thrown validation error messages |
| 41 | + |
| 42 | +A new `overrideErrorMessage` function has been added to the `throwValidationErrors` utility, allowing you to customize error messages: |
| 43 | + |
| 44 | +```typescript |
| 45 | +import { throwValidationErrors, overrideErrorMessage } from "next-safe-action"; |
| 46 | + |
| 47 | +actionClient |
| 48 | + .inputSchema((s) => ({ |
| 49 | + username: s.string().minLength(3) |
| 50 | + })) |
| 51 | + .action(async ({ parsedInput: { username } }) => { |
| 52 | + // Custom validation error with overridden message |
| 53 | + if (usernameExists(username)) { |
| 54 | + throwValidationErrors({ |
| 55 | + username: overrideErrorMessage("This username is already taken") |
| 56 | + }); |
| 57 | + } |
| 58 | + |
| 59 | + // ... |
| 60 | + }); |
| 61 | +``` |
| 62 | + |
| 63 | +### Navigation callbacks |
| 64 | + |
| 65 | +A new `onNavigation` callback has been added to both actions and hooks, which is triggered when navigation functions from `next/navigation` are called in action execution: |
| 66 | + |
| 67 | +```typescript |
| 68 | +import { useAction } from "next-safe-action/hooks"; |
| 69 | +import { redirect } from "next/navigation"; |
| 70 | + |
| 71 | +// In the action definition |
| 72 | +const action = actionClient |
| 73 | + .inputSchema((s) => ({ id: s.string() })) |
| 74 | + .action(async ({ parsedInput: { id } }) => { |
| 75 | + // ... process data |
| 76 | + redirect(`/details/${id}`); |
| 77 | + }); |
| 78 | + |
| 79 | +// In the component |
| 80 | +const { execute } = useAction(action, { |
| 81 | + onNavigation: ({ type, destination }) => { |
| 82 | + console.log(`Navigation of type ${type} to ${destination}`); |
| 83 | + } |
| 84 | +}); |
| 85 | +``` |
| 86 | + |
| 87 | +## BREAKING CHANGES |
| 88 | + |
| 89 | +### Removal of validation adapters |
| 90 | + |
| 91 | +All external validation library adapters have been removed in favor of the built-in Standard Schema. This means you need to migrate your validation schemas to Standard Schema syntax. |
| 92 | + |
| 93 | +### `schema` method renamed to `inputSchema` |
| 94 | + |
| 95 | +The `schema` method has been renamed to `inputSchema` to better reflect its purpose: |
| 96 | + |
| 97 | +```typescript |
| 98 | +// v7 |
| 99 | +actionClient.schema(/* ... */) |
| 100 | + |
| 101 | +// v8 |
| 102 | +actionClient.inputSchema(/* ... */) |
| 103 | +``` |
| 104 | + |
| 105 | +The `schema` method is deprecated and will be removed in a future version. |
| 106 | + |
| 107 | +### Bind args validation errors now throw instead of returning |
| 108 | + |
| 109 | +When using bind arguments with invalid data, errors are now thrown instead of being returned as part of the result object: |
| 110 | + |
| 111 | +```typescript |
| 112 | +// v7 |
| 113 | +const result = await action.bind(null, invalidBindArg)(input); |
| 114 | +if (result.validationErrors) { |
| 115 | + // Handle bind args validation errors |
| 116 | +} |
| 117 | + |
| 118 | +// v8 |
| 119 | +try { |
| 120 | + const result = await action.bind(null, invalidBindArg)(input); |
| 121 | +} catch (error) { |
| 122 | + // Bind args validation errors now throw |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +### Safe action result always defined |
| 127 | + |
| 128 | +The result of action execution is now always defined, even when there are validation errors: |
| 129 | + |
| 130 | +```typescript |
| 131 | +// v7 |
| 132 | +const result = await action(input); |
| 133 | +if (result.data) { |
| 134 | + // Only access data if it exists |
| 135 | +} |
| 136 | + |
| 137 | +// v8 |
| 138 | +const result = await action(input); |
| 139 | +// result.data is always defined (unless there's a server error) |
| 140 | +``` |
| 141 | + |
| 142 | +### Removed `executeOnMount` functionality from hooks |
| 143 | + |
| 144 | +The `executeOnMount` option has been removed from hooks: |
| 145 | + |
| 146 | +```typescript |
| 147 | +// v7 |
| 148 | +const { data } = useAction(action, { |
| 149 | + executeOnMount: true, |
| 150 | + initialInput: { name: "John" } |
| 151 | +}); |
| 152 | + |
| 153 | +// v8 - no longer supported |
| 154 | +// You should execute the action manually when the component mounts |
| 155 | +const { execute, data } = useAction(action); |
| 156 | +useEffect(() => { |
| 157 | + execute({ name: "John" }); |
| 158 | +}, []); |
| 159 | +``` |
| 160 | + |
| 161 | +### Deprecated `useStateAction` hook |
| 162 | + |
| 163 | +The `useStateAction` hook has been deprecated. Consider using the regular `useAction` hook instead. |
| 164 | + |
| 165 | +## Improved error handling |
| 166 | + |
| 167 | +Thrown errors in hooks now properly set the `hasErrored` status and trigger the `onError` callback: |
| 168 | + |
| 169 | +```typescript |
| 170 | +const { execute, hasErrored } = useAction(action, { |
| 171 | + onError: (error) => { |
| 172 | + console.error("Action failed:", error); |
| 173 | + } |
| 174 | +}); |
| 175 | +``` |
| 176 | + |
| 177 | +## Requirements |
| 178 | + |
| 179 | +next-safe-action version 8 requires Next.js 14 and React 18.2.0 or later to work. |
| 180 | + |
| 181 | +## What about v7? |
| 182 | + |
| 183 | +You can still keep using version 7 and eventually upgrade to version 8. Note that version 7 is frozen and no new features will be released in the future for it. v7 documentation can still be found [here](https://v7.next-safe-action.dev). |
0 commit comments