payload icon indicating copy to clipboard operation
payload copied to clipboard

lexicalHTML field doesn't update in client-side Live Preview

Open stephtr opened this issue 1 year ago • 8 comments

Link to reproduction

No response

Environment Info

Binaries:
  Node: 20.9.0
  npm: 10.1.0
  Yarn: 1.22.19
  pnpm: 9.7.1
Relevant Packages:
  payload: 3.0.0-beta.107
  next: 15.0.0-canary.136
  @payloadcms/graphql: 3.0.0-beta.107
  @payloadcms/live-preview: 3.0.0-beta.107
  @payloadcms/live-preview-react: 3.0.0-beta.107
  @payloadcms/next/utilities: 3.0.0-beta.107
  @payloadcms/richtext-lexical: 3.0.0-beta.107
  @payloadcms/translations: 3.0.0-beta.29
  @payloadcms/ui/shared: 3.0.0-beta.107
  react: 19.0.0-rc-e56f4ae3-20240830
  react-dom: 19.0.0-rc-e56f4ae3-20240830
Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.0.0: Mon Aug 12 20:52:41 PDT 2024; root:xnu-11215.1.10~2/RELEASE_ARM64_T6031
  Available memory (MB): 36864
  Available CPU cores: 14

Describe the Bug

When adding a rich text field and a lexicalHTML field for automatically converting it into HTML code, as well as enabling client-side Live Preview, only the rich text field content gets updated while typing, but not the generated HTML field.

Is this a bug or a design decision? If a bug, I could quickly create a repo for reproduction.

Reproduction Steps

Adapters and Plugins

No response

stephtr avatar Sep 18 '24 12:09 stephtr

I'm also facing this issue and curious how it might be resolved; the experience for editing a lexicalHTML field is less than ideal in the current state of the live preview feature.

alexnitta avatar Sep 19 '24 22:09 alexnitta

I guess you anyway know, but you could also switch to server-side live-preview together with Versions/Draft-mode and Autosave. Overall, I find this the even better experience.

stephtr avatar Sep 28 '24 11:09 stephtr

@stephtr I was not aware of that option; I'll look into it. Thanks for the tip!

alexnitta avatar Sep 30 '24 18:09 alexnitta

@stephtr to be clear, are you saying that we should see the generated HTML field update after an autosave? I tried setting up server-side live preview with versions/draft-mode and autosave, but I didn't see the generated HTML update in the live preview. I'm on an outdated version of the Payload beta, so that could be my issue, but I wasn't actually sure if you expected this to fix the problem.

alexnitta avatar Oct 09 '24 21:10 alexnitta

facing the same issue on payload version: 2.30.1. I am guessing the afterRead hook that lexicalHTML uses behind the scenes is not re-running before the updated data is passed back to the live server client.

afreidz avatar Oct 11 '24 21:10 afreidz

@alexnitta yes, for me that worked (I was running beta 107). You then either have about 2 seconds delay until autosave kicks in or decrease the autosave interval

stephtr avatar Oct 12 '24 10:10 stephtr

Any updates on this? Server-side live preview is all good and well for frameworks with server-side components, but those of us using Vue3, for example, don't have that option.

m-shum avatar Oct 17 '24 14:10 m-shum

This issue has been marked as stale due to lack of activity.

To keep this issue open, please indicate that it is still relevant in a comment below.

github-actions[bot] avatar Dec 13 '24 05:12 github-actions[bot]

This issue was automatically closed due to lack of activity.

github-actions[bot] avatar Dec 21 '24 05:12 github-actions[bot]

I'm facing same issue, the lexicalHTML is not callable on [slug]/page.ts .... steps:

fields: [ { name: "title", type: "text", required: true, label: "Post Title", hooks: { beforeValidate: [ ({ value, data, operation }: FieldHookArgs<PostType>) => { if (operation === "create" && data && (!data.slug || data.slug === "") && value) { data.slug = value .toLowerCase() .replace(/\s+/g, "-") .replace(/[^a-z0-9-]/g, ""); } return value; }, ], }, }, { name: "slug", type: "text", required: false, unique: false, admin: { description: "Auto-generated URL path", }, }, { name: "lexicalContent", type: "richText", editor: lexicalEditor({ features: ({ defaultFeatures }) => [ ...defaultFeatures, HTMLConverterFeature({}),

      LinkFeature({
        fields: ({ defaultFields }) => [
          ...defaultFields,
          {
            name: "rel",
            label: "Rel Attribute",
            type: "select",
            hasMany: true,
            options: ["noopener", "noreferrer", "nofollow"],
            admin: {
              description: "Define relationship attributes for links.",
            },
          },
        ],
      }),

      UploadFeature({
        collections: {
          uploads: {
            fields: [
              {
                name: "caption",
                type: "richText",
                editor: lexicalEditor(),
              },
            ],
          },
        },
      }),
      BlocksFeature({
        blocks: [Banner, CallToAction, ImageGrid],
      }),
    ],
  }),

  required: false,
  label: "Post Content",

  admin: {
    description: "Add the main content of the post.",
  },
},

lexicalHTML("lexicalContent", { name: "lexicalContentConverted_html" }),

--- Page.ts: // app/posts/[slug]/page.tsx

//@ts-ignore import { draftMode } from "next/headers"; //@ts-ignore import { notFound } from "next/navigation"; import { getPayload } from "payload"; import configPromise from "@payload-config"; import Posts from "../../../collections/Posts.ts";

import type { HTMLConverter } from "@payloadcms/richtext-lexical"; import { lexicalEditor } from "@payloadcms/richtext-lexical"; import { lexicalHTML } from "@payloadcms/richtext-lexical"; import { RefreshRouteOnSave } from "./RefreshRouteOnSave.tsx"; import LexicalContentRenderer from "@/components/LexicalHTMLRenderer.tsx";

async function getPost(slug: string, isDraft: boolean) { const payload = await getPayload({ config: configPromise });

const post = await payload.find({ collection: "posts", where: { slug: { equals: slug, }, }, draft: isDraft, // Fetch draft content if preview mode is enabled });

return post.docs[0]; }

export default async function PostPage({ params }: { params: { slug: string } }) { const { isEnabled } = await draftMode(); const { slug } = await params; console.log(slug, isEnabled); const post = await getPost(slug, isEnabled);

console.log(await post.title); console.log(await post.content); //const stingifiedContent = post.content;

//const postContent = JSON.stringify(post.content);

console.log("Post Content (Type):", typeof post.content); console.log( "Post Content (Value):", // <LexicalContentRenderer lexicalContent={post.lexicalContent} />, );

//console.log(lexicalHTML(lexicalContent, lexicalContentConverted_html)); ---> Want to call this, but unable to, please tell the way to call this function.

//const con = lexicalHTML("lexicalContent", { name: "lexicalContentConverted_html" }); // const stringifydContent = JSON.stringify(con); //console.log("Raw Lexical Content - lexicalContent (JSON) to String:", stringifydContent); // console.log("Raw Lexical Content - lexicalContent TYPE :", typeof stringifydContent);

console.log("Post Object:", post);

if (!post) { notFound(); }

// Parse the content field if it's a string let parsedContent; try { parsedContent = typeof post.content === "string" ? JSON.parse(post.content) : post.content; } catch (error) { console.error("Failed to parse content:", error); parsedContent = null; } console.log("Parsed Lexical Content:", parsedContent);

// console.log("Converted HTML:", post.lexicalContentConverted_html);

// Handle categories and tags const categories = Array.isArray(post.categories) ? post.categories : []; const tags = Array.isArray(post.tags) ? post.tags : [];

// Render the post content return ( <> <RefreshRouteOnSave /> <div style={{ padding: "20px", fontFamily: "Arial, sans-serif" }}>

{post.title}

Slug: {post.slug}

    <div>
      <strong>Content:</strong>{" "}
      {parsedContent ? (
        <LexicalContentRenderer content={parsedContent} />
      ) : (
        <p>Failed to load content.</p>
      )}
    </div>
    <p>
      <strong>Excerpt:</strong> {post.excerpt || "No excerpt available."}
    </p>
    <p>
      <strong>Author:</strong> {post.author || "Unknown"}
    </p>
    <p>
      <strong>Status:</strong> {post.status}
    </p>
    <p>
      <strong>Published Date:</strong> {post.publishedDate || "Not published yet."}
    </p>
    <p>
      <strong>Post Type:</strong> {post.postType || "N/A"}
    </p>
    <p>
      <strong>Visibility:</strong> {post.visibility || "Public"}
    </p>
    <p>
      <strong>Categories:</strong>
      {categories.length > 0
        ? categories.map((category: string, index: number) => (
            <span key={index}>{category}</span>
          ))
        : "None"}
    </p>
    <p>
      <strong>Tags:</strong>
      {tags.length > 0
        ? tags.map((tag: string | { name: string }, index: number) => (
            <span key={index}>{typeof tag === "string" ? tag : tag.name}</span>
          ))
        : "None"}
    </p>
    {post.featuredImage &&
      typeof post.featuredImage === "object" &&
      "url" in post.featuredImage && (
        <div>
          <strong>Featured Image:</strong>
          <img
            src={post.featuredImage.url || undefined}
            alt="Featured"
            style={{ maxWidth: "100%" }}
          />
        </div>
      )}
  </div>
</>

); }


import React from "react";

interface LexicalContentRendererProps { content: { root: { children: Array; direction: string; format: string; indent: number; type: string; version: number; }; }; }

const LexicalContentRenderer: React.FC<LexicalContentRendererProps> = ({ content }) => { const { root } = content;

if (!root || !Array.isArray(root.children)) { return

No content available.

; }

return (

{root.children.map((child, index) => { if (child.type === "paragraph") { return (

{child.children && child.children.map((textNode: any, textIndex: number) => { if (textNode.type === "text") { return {textNode.text}; } return null; })}

); } return null; })}
); };

export default LexicalContentRenderer;

/* import React from "react";

interface LexicalHTMLRendererProps { html: string; }

const LexicalHTMLRenderer: React.FC<LexicalHTMLRendererProps> = ({ html }) => { if (!html) { return

No content available.

; }

return ( <div dangerouslySetInnerHTML={{ __html: html, }} /> ); };

export default LexicalHTMLRenderer; */

--whereas, I don't want to do all this, it's a headache to figure out a way to make this parsed correctly.

--> But when I try to call this on page.ts, it doesn't even recognize this, Don't even know how to call/pass this lexicalHTML, lexicalContentConverted_html to make it work, as this is not even recognized as props or anything like that. Please give code on how to use lexicalHTML, for live-preview in lexical format for the post.content field. Thank you. Waiting for reply eagerly.

P.S- also request you to update the Payload CMS documentation for lexicalHTML for live-preview, post.content field as an example, how to use HTMLConverters, as this is very very important, else we keep getting this error: payload Objects are not valid as a React child (found: object with keys {root}). If you meant to render a collection of children, use an array instead.

MongoDB: _id 67a800c8169258c4c1c82a1b parent 67a800c8169258c4c1c82a19

version Object title "krishna rama govinda" slug "krishna-rama-govinda"

lexicalContent Object

root Object

children Array (1)

0 Object

children Array (1)

0 Object detail 0 format 0 mode "normal" style "" text "ramamamram-RAM" type "text" version 1 direction "ltr" format "" indent 0 type "paragraph" version 1 textFormat 0 textStyle "" direction "ltr" format "" indent 0 type "root" version 1 lexicalContentConverted_html null

excerpt Object

root Object

children Array (1)

0 Object

children Array (1)

0 Object detail 0 format 0 mode "normal" style "" text "RAM" type "text" version 1 direction "ltr" format "" indent 0 type "paragraph" version 1 textFormat 0 textStyle "" direction "ltr" format "" indent 0 type "root" version 1

heroSliderImages Array (empty) status "draft" updatedAt 2025-02-09T01:11:36.620+00:00 createdAt 2025-02-09T01:11:36.620+00:00 _status "draft" createdAt 2025-02-09T01:11:36.892+00:00 updatedAt 2025-02-09T01:11:36.892+00:00 latest true __v 0

this is my output console logs: Access granted: Logged-in user GET /admin/collections/posts/67a68874a3650024fb5d834c/preview 200 in 5540ms om-sairam-swami--testing-content false Om Sairam Swami- Testing Content undefined Post Content (Type): undefined Post Content (Value): Post Object: { id: '67a68874a3650024fb5d834c', title: 'Om Sairam Swami- Testing Content', slug: 'om-sairam-swami--testing-content', lexicalContent: { root: { children: [Array], direction: 'ltr', format: '', indent: 0, type: 'root', version: 1 } }, lexicalContentConverted_html: '

Hare Rama Hare Rama Rama Rama Hare Hare

', excerpt: { root: { children: [Array], direction: 'ltr', format: '', indent: 0, type: 'root', version: 1 } }, heroSliderImages: [], postType: 'article', author: 'Developer', status: 'draft', publishedDate: '2025-02-08T11:30:00.000Z', _status: 'draft', createdAt: '2025-02-07T22:25:56.459Z', updatedAt: '2025-02-07T22:25:56.459Z' } Parsed Lexical Content: undefined ⨯ [Error: Objects are not valid as a React child (found: object with keys {root}). If you meant to render a collection of children, use an array instead.] { digest: '2907643195' } GET /posts/om-sairam-swami--testing-content?preview=true 500 in 4647ms

THIS IS AN ISSUE, AS THIS FUNCTION LEXICALHTML IS NOT AVAILABLE FOR ARRAYS I GUESS, STILL, AS I UNDERSTAND FROM THIS DOCUMENTATION OF YOURS: https://payloadcms.com/community-help/discord/how-to-implement-lexical-to-html-converter-on-the-server (REQUEST YOUR ESTEEMED ASSISTANCE ASAP/ WORKAROUND CODE).

[Bug] Server-side Lexical to HTML serializer is not working for nested fields #4034 #4440 #4103

namitasai avatar Feb 08 '25 23:02 namitasai

I am also facing this issue, however I don't have the option to switch to server-side live preview, because I am using Remix on the frontend. And I cannot use JSON, because the RichText response can contain lots and lots of deeply nested relation and link objects ... Would be cool, if client-side preview would enable this option, or maybe we can switch to JSON when doing preview and use HTML when doing preview

HriBB avatar Feb 12 '25 16:02 HriBB

#> I am also facing this issue, however I don't have the option to switch to server-side live preview, because I am using Remix on the frontend. And I cannot use JSON, because the RichText response can contain lots and lots of deeply nested relation and link objects ... Would be cool, if client-side preview would enable this option, or maybe we can switch to JSON when doing preview and use HTML when doing preview

@HriBB try to do serialization of the fields where you are using lexicalEditor, that generates Lexical JSON, which will not accept Rich HTML format or strings, so do serialization for those payload fields such as content, title, excerpt, blocks, upload or whatever else you are using, then you can use those serialiazed fields on client side also or preview. Basically PayloadCMS should fix their HTMLConverter(), LexicalHTML(), so we are making workarounds by writing so much code, it is a lot of work. Steps for you: Define those fields in Collection Posts for example, as richText, LexicalEditor() (2) use hooks: beforeChange() -- write your logic to trigger conversion of lexicalJSON to HTML before saving to db 3) call it in [slug]/page.tsx NOTE: Caveat: it is not as easy, because you also need to write conversion logic like LexicalRender functions or RichTextRender whatever you can call it, then pass it in hooks, but i hope I've given you some idea, PLEASE DON'T MIND @PAYLOADCMS: I TRULY WISH, PAYLOAD CMS CAN IMPROVE THEIR DOCUMENTATION, GIVE WORKING EXAMPLES FOR EACH FEATURE THEY LAUNCH BY GIVING END TO END EXAMPLES ON GITHUB THAN LEAVING US FOR DOING HIT AND TRY PLAYING WITH EACH AND EVERY COMMAND OF PAYLOAD CMS. I TRIED TO CONTACT THEM VIA THEIR FORM AS NO PHONE NUMBER IS AVAILABLE; SOMEONE CONTACTED ME ON THIS TO GET MORE INFORMATION BUT THEY NEVER KEPT ME INFORMED ON THE STATUS FOR THIS OR PROVIDED WORKAROUND OR CONNECTED ME WITH THE TECH ON THIS. YOU CAN TRY TO CONTACT THEM ON THIS AS WELL.

namitasai avatar Feb 13 '25 16:02 namitasai

The issue https://github.com/payloadcms/payload/issues/8168 describes the same problem from a more technical perspective.

I just wrote a comment there explaining how this is actually mostly a documentation issue and the recommended ways to resolve it. I'll soon improve the documentation with the information described in that comment.

Thanks everyone for your patience, and if you have any questions, let me know!

GermanJablo avatar May 20 '25 15:05 GermanJablo

🚀 This is included in version v3.39.0

github-actions[bot] avatar May 22 '25 14:05 github-actions[bot]

This issue has been automatically locked. Please open a new issue if this issue persists with any additional detail.

github-actions[bot] avatar May 30 '25 05:05 github-actions[bot]

Fixed by https://github.com/payloadcms/payload/pull/13619 - the lexicalHTML field is now supported in client-side live preview!

AlessioGr avatar Sep 05 '25 18:09 AlessioGr