fix(ui): autosave hooks are not reflected in form state (#13416)
Fixes #10515. Needed for #12956. Hooks run within autosave are not reflected in form state. Similar to #10268, but for autosave events. For example, if you are using a computed value, like this: ```ts [ // ... { name: 'title', type: 'text', }, { name: 'computedTitle', type: 'text', hooks: { beforeChange: [({ data }) => data?.title], }, }, ] ``` In the example above, when an autosave event is triggered after changing the `title` field, we expect the `computedTitle` field to match. But although this takes place on the database level, the UI does not reflect this change unless you refresh the page or navigate back and forth. Here's an example: Before: https://github.com/user-attachments/assets/c8c68a78-9957-45a8-a710-84d954d15bcc After: https://github.com/user-attachments/assets/16cb87a5-83ca-4891-b01f-f5c4b0a34362 --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1210561273449855
This commit is contained in:
@@ -84,7 +84,7 @@ export interface Config {
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: number;
|
||||
defaultIDType: string;
|
||||
};
|
||||
globals: {
|
||||
menu: Menu;
|
||||
@@ -124,7 +124,7 @@ export interface UserAuthOperations {
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: number;
|
||||
id: string;
|
||||
title?: string | null;
|
||||
content?: {
|
||||
root: {
|
||||
@@ -149,7 +149,7 @@ export interface Post {
|
||||
* via the `definition` "media".
|
||||
*/
|
||||
export interface Media {
|
||||
id: number;
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
@@ -193,7 +193,7 @@ export interface Media {
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: number;
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
@@ -217,24 +217,24 @@ export interface User {
|
||||
* via the `definition` "payload-locked-documents".
|
||||
*/
|
||||
export interface PayloadLockedDocument {
|
||||
id: number;
|
||||
id: string;
|
||||
document?:
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: number | Post;
|
||||
value: string | Post;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'media';
|
||||
value: number | Media;
|
||||
value: string | Media;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@@ -244,10 +244,10 @@ export interface PayloadLockedDocument {
|
||||
* via the `definition` "payload-preferences".
|
||||
*/
|
||||
export interface PayloadPreference {
|
||||
id: number;
|
||||
id: string;
|
||||
user: {
|
||||
relationTo: 'users';
|
||||
value: number | User;
|
||||
value: string | User;
|
||||
};
|
||||
key?: string | null;
|
||||
value?:
|
||||
@@ -267,7 +267,7 @@ export interface PayloadPreference {
|
||||
* via the `definition` "payload-migrations".
|
||||
*/
|
||||
export interface PayloadMigration {
|
||||
id: number;
|
||||
id: string;
|
||||
name?: string | null;
|
||||
batch?: number | null;
|
||||
updatedAt: string;
|
||||
@@ -393,7 +393,7 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||
* via the `definition` "menu".
|
||||
*/
|
||||
export interface Menu {
|
||||
id: number;
|
||||
id: string;
|
||||
globalText?: string | null;
|
||||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
|
||||
@@ -53,6 +53,14 @@ const AutosavePosts: CollectionConfig = {
|
||||
unique: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'computedTitle',
|
||||
label: 'Computed Title',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
beforeChange: [({ data }) => data?.title],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
|
||||
@@ -1285,6 +1285,44 @@ describe('Versions', () => {
|
||||
// Remove listener
|
||||
page.removeListener('dialog', acceptAlert)
|
||||
})
|
||||
|
||||
test('- with autosave - applies afterChange hooks to form state after autosave runs', async () => {
|
||||
const url = new AdminUrlUtil(serverURL, autosaveCollectionSlug)
|
||||
await page.goto(url.create)
|
||||
const titleField = page.locator('#field-title')
|
||||
await titleField.fill('Initial')
|
||||
await waitForAutoSaveToRunAndComplete(page)
|
||||
const computedTitleField = page.locator('#field-computedTitle')
|
||||
await expect(computedTitleField).toHaveValue('Initial')
|
||||
})
|
||||
|
||||
test('- with autosave - does not display success toast after autosave complete', async () => {
|
||||
const url = new AdminUrlUtil(serverURL, autosaveCollectionSlug)
|
||||
await page.goto(url.create)
|
||||
const titleField = page.locator('#field-title')
|
||||
await titleField.fill('Initial')
|
||||
|
||||
let hasDisplayedToast = false
|
||||
|
||||
const startTime = Date.now()
|
||||
const timeout = 5000
|
||||
const interval = 100
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
const isHidden = await page.locator('.payload-toast-item').isHidden()
|
||||
console.log(`Toast is hidden: ${isHidden}`)
|
||||
|
||||
// eslint-disable-next-line playwright/no-conditional-in-test
|
||||
if (!isHidden) {
|
||||
hasDisplayedToast = true
|
||||
break
|
||||
}
|
||||
|
||||
await wait(interval)
|
||||
}
|
||||
|
||||
expect(hasDisplayedToast).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Globals - publish individual locale', () => {
|
||||
|
||||
@@ -197,6 +197,7 @@ export interface Post {
|
||||
export interface AutosavePost {
|
||||
id: string;
|
||||
title: string;
|
||||
computedTitle?: string | null;
|
||||
description: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@@ -366,7 +367,6 @@ export interface Diff {
|
||||
textInNamedTab1InBlock?: string | null;
|
||||
};
|
||||
textInUnnamedTab2InBlock?: string | null;
|
||||
textInUnnamedTab2InBlockAccessFalse?: string | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'TabsBlock';
|
||||
@@ -469,7 +469,6 @@ export interface Diff {
|
||||
};
|
||||
textInUnnamedTab2?: string | null;
|
||||
text?: string | null;
|
||||
textCannotRead?: string | null;
|
||||
textArea?: string | null;
|
||||
upload?: (string | null) | Media;
|
||||
uploadHasMany?: (string | Media)[] | null;
|
||||
@@ -787,6 +786,7 @@ export interface PostsSelect<T extends boolean = true> {
|
||||
*/
|
||||
export interface AutosavePostsSelect<T extends boolean = true> {
|
||||
title?: T;
|
||||
computedTitle?: T;
|
||||
description?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
@@ -960,7 +960,6 @@ export interface DiffSelect<T extends boolean = true> {
|
||||
textInNamedTab1InBlock?: T;
|
||||
};
|
||||
textInUnnamedTab2InBlock?: T;
|
||||
textInUnnamedTab2InBlockAccessFalse?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
@@ -995,7 +994,6 @@ export interface DiffSelect<T extends boolean = true> {
|
||||
};
|
||||
textInUnnamedTab2?: T;
|
||||
text?: T;
|
||||
textCannotRead?: T;
|
||||
textArea?: T;
|
||||
upload?: T;
|
||||
uploadHasMany?: T;
|
||||
|
||||
Reference in New Issue
Block a user