Functional default component and ESLint error: " A decorated export must export a class declaration."

I used the plugin generator to create a scaffold for a test plugin. I’m trying to convert the class component Main into a functional one, but getting this error in ESLint: " A decorated export must export a class declaration."

I don’t know what a “decorated export” is, but I think it’s related to:

@connectToDatoCms(plugin => ({
  fieldValue: plugin.getFieldValue(plugin.fieldPath),

What does the @ symbol mean and why is it causing errors? Sorry, I’ve never seen these “decorators” before and not sure what they’re for.

I was really confused by the generated example that datocms-plugin-generator created using an older version of React; its toolchain (webpack, babel, eslint, etc.) also seemed to ship with older defaults.

If anyone else is struggling with this, I ended up just using create-react-app with modern hooks and that worked much better.

In the plugin settings, I had to change the entry point to localhost:3000 (the create-react-app default) instead of 5000 (used in the plugin generator).

Using hooks, the code looks like this:

import {useState, useEffect} from "react";
import DatoCmsPlugin from 'datocms-plugins-sdk';

export default function App() {
    const [isLoading, setLoading] = useState(true);
    const [datoData, setDatoData] = useState();

    useEffect(() => {
            DatoCmsPlugin.init(function (plugin) {
                setDatoData({parameters: plugin.parameters, field: plugin.field});
        }, [datoData]

    return (
            <h1>DatoCMS output</h1>
            {isLoading ? "Loading, please wait..." : JSON.stringify(datoData, null, 2)};

It’s much more straightforward than the class-based skeleton that the plugin creates.

In the content editor, this looks like:

Note: That sample above only works for display, not yet for editing anything. I think the useEffect hook is also unnecessary there.

I’m still trying to figure out how to send data back to Dato asynchronously. plugin.setFieldValue() returns a promise… not sure how to resolve that with React states. I’ll update this if I figure it out.

OK, this is what seems to work…

const [JSONData, setJSONData] = useState({});

DatoCmsPlugin.init(plugin => {

useEffect(() => {
        DatoCmsPlugin.init(plugin => {
            setJSONData(JSON.parse(plugin.getFieldValue(plugin.fieldPath))) // This is a JSON field
    }, []

const sendJSONToDato = async newJSON => {
    DatoCmsPlugin.init(plugin => {
        plugin.setFieldValue(plugin.fieldPath, JSON.stringify(newJSON))
    }).then(() =>

The state (JSONData) keeps track of local state. useEffect loads the field value from Dato into the local state only once (because of the empty [] deps array).

Then, whenever something is updated by a human, I call sendJSONToDato instead of setJSONData directly. That makes an async call to Dato, waits for the promises to resolve, and then sets the local state.

It seems to work in basic cases. No error handling or conflict resolution, etc. though. If multiple editors try to work on the same record it’ll probably blow up.

Thanks @roger, yes, we should really update our skeleton. Added in our TODO list, will update this thread as soon as it’s ready.

By the way, whenever an editor starts editing a record the record is locked and other editors cannot edit it at the same time: Collaboration features - DatoCMS

1 Like

Great, thanks s.verna! I’m just glad that you have the plugin functionality at all. Looking forward to seeing what we can build with it :slight_smile:

1 Like

Yeoman generator just upgraded! Uses React hooks, latest DatoCMS plugin SDK!

1 Like