Nextjs - trouble switching to MarkdownFieldPlugin

I setup TinaCMS with my Nextjs blog/site. It worked - except that the main body area in the sidebar needed to be switched to a plugin.
I have been unable to fully incorporate this switch. I feel like I must be missing something very simple - (fingers crossed) I have read documentation and looked at several examples.

I am stuck - I have reached the edge of what I am capable of on my own. Thank you for any insight.
Here is the error, if helpful:
UnhandledPromiseRejectionWarning: ReferenceError: cms is not defined at Promise.resolve.then.then (webpack-internal:///./pages/posts/[slug].js:52:3) (node:20071) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 3)
git repo is: https://github.com/WendyKKelly/Underbelly-Basic.git

Thank you.

Looks like you need to define the cms first here: https://tinacms.org/docs/getting-started/cms-set-up/

thank you so much - I believe I have narrowed down where the error lies. It is on the [slug].js page.

I am happy to provide any information that is helpful, etc.

This is where I am unsure of exactly what code is needed:

Any help is greatly appreciated.

Welcome to the forum @Wendy!

It looks like the problem is on line 15 of that [slug].js file, where you are importing react-tinacms-editor. The error is occurring because the cms variable is not available in the global scope. When dynamically importing the editor, you need to do it in a component scope that has access to the CMS instance.

You can find an example of dynamically importing react-tinacms-editor here: https://github.com/tinacms/tinacms/blob/master/packages/demo-gatsby/src/templates/blog-post.js#L43

In your case, you should be able to do something similar inside of your Post component:

import { useEffect, useState } from 'react'
import { useCMS } from 'tinacms'

export default function Post({ post: initialPost, morePosts, preview }) {
  const cms = useCMS()
  const [editorRegistered, setEditorRegistered] = useState(false)
  
  useEffect(() => {
    if (!editorRegistered && cms.enabled) {
      import("react-tinacms-editor").then(({ MarkdownFieldPlugin, HtmlFieldPlugin }) => {
        cms.plugins.add(MarkdownFieldPlugin)
        cms.plugins.add(HtmlFieldplugin)
        setEditorRegistered(true)
      })
    }
  }, [cms.enabled])
  //...
}


It’s also worth noting that you don’t have to dynamically import react-tinacms-editor, it’s just recommended for page performance. If you don’t care about dynamic imports, you could instead do the following in your _app.js file:

import React from 'react'
import App from 'next/app'
import { withTina } from 'tinacms'
import { MarkdownFieldPlugin, HtmlFieldPlugin } from 'react-tinacms-editor'

function MyApp ({ Component, pageProps }) {
  return <Component {...pageProps} />
};

export default withTina(MyApp, {
  enabled: true,
  sidebar: true,
  plugins: [
    MarkdownFieldPlugin,
    HtmlFieldPlugin
  ],
});
2 Likes

that is so helpful. Thank you – I look forward to the day I can help someone myself!!

After that – when accessing the plugin…I feel silly asking this…but how, exactly does that work?

What does that look like?

this is the solution I came up with … but I still can’t seem to figure out the accessing part? No errors just a sidebar that tells me to use the plugins.

(As you can see - I ended up just copying in almost verbatim from the documentation how to access the plugin – It is something I honestly – it’s beyond my scope…)

import { TinaCMS, useForm, usePlugin } from 'tinacms'

import { HtmlFieldPlugin, MarkdownFieldPlugin } from ‘react-tinacms-editor’

const cms = new TinaCMS({ enabled: true })

cms.plugins.add(HtmlFieldPlugin)
cms.plugins.add(MarkdownFieldPlugin)

export default function Post({ post: initialPost, morePosts, preview }) {
const router = useRouter()
if (!router.isFallback && !initialPost?.slug) {
return
}

const sayHello = React.useCallback(() => {
// get all of the “hello” plugins.
const helloPlugins = cms.plugins.all(post.content)

// iterate over all of the "hello" plugins
helloPlugins.forEach(plugin => alert(`Hello, ${plugin.user}!`))

}, [])
const formConfig =
{
id: initialPost.slug, // a unique identifier for this instance of the form
label: ‘Blog Post’, // name of the form to appear in the sidebar
initialValues: initialPost, // populate the form with starting values
onSubmit: values => {
// do something with the data when the form is submitted
alert(Submitting ${values.title})
},
fields: [
{
name: “description”,
label: “Description”,
component: “html”,
},
{
name: ‘rawMarkdownBody’,
label: ‘Content’,
component: ‘markdown’,
},
],
}
const [post, form] = useForm(formConfig)
usePlugin(form)

return (

<>


{router.isFallback ? (
Loading…
) : (
<>



{post.title} | The Underbelly Blog






</>
)}

</>

)
}

export async function getStaticProps({ params }) {
const post = getPostBySlug(params.slug, [
‘title’,
‘date’,
‘slug’,
‘author’,
‘content’,
‘ogImage’,
‘coverImage’,
])
const content = await markdownToHtml(post.content || ‘’)

return {
props: {
post: {
…post,
content,
rawMarkdownBody: post.content,
},
},
}
}

export async function getStaticPaths() {
const posts = getAllPosts([‘slug’])

return {
paths: posts.map((posts) => {
return {
params: {
slug: posts.slug,
},
}
}),
fallback: false,
}
}

In your example, you’ve registered a new copy of the CMS in the global scope which isn’t used anywhere. It doesn’t matter if you register plugins with this new copy, it won’t do anything.

Your application will create the CMS exactly once, and that same instance is shared throughout the rest of your application. This is done by creating a context object that lets other components in the application use it.

This is happening in your _app.js file when you call withTina. That’s why, in the second code example I provided above, I imported and included the plugins there and not at the top of [slug].js in the global scope.

If you re-read my previous post carefully and try one of the two options I provided, you should have more success!

Yes!!!

So, so sorry!! I was impatient, I guess. I did reread your post and did figure it out and was just going to delete my reply :slight_smile:

Wonderful.

Dang, I love this stuff so much! Thank you soooo much.

3 Likes