Mixed field - saving on frontend gives an error

I am going through the tutorial and trying to setup a mixed field. Everything seems to work, I get the grid display on the frontend, but when I try to save I get the following Error:

Objects are not valid as a React child (found: object with keys {items}). If you meant to render a collection of children, use an array instead.

at span
at TextWidget (http://localhost:3001/static/js/client.js:151254:5)
at div
at Segment (http://localhost:3001/static/js/client.js:245968:24)
at div
at GridRow (http://localhost:3001/static/js/client.js:241080:24)
at div
at Grid (http://localhost:3001/static/js/client.js:240856:22)
at div
at div
at Container (http://localhost:3001/static/js/client.js:243436:24)
at DefaultView (http://localhost:3001/static/js/client.js:148535:5)
at injectIntl(DefaultView)
at div
at View (http://localhost:3001/static/js/client.js:150247:5)
at ConnectFunction (http://localhost:3001/static/js/client.js:211057:68)
at injectIntl(Connect(View))
at Route (http://localhost:3001/static/js/client.js:216619:29)
at Switch (http://localhost:3001/static/js/client.js:216825:29)
at main
at div
at Segment (http://localhost:3001/static/js/client.js:245968:24)
at MultilingualRedirector (http://localhost:3001/static/js/client.js:144417:5)
at PluggablesProvider (http://localhost:3001/static/js/client.js:122438:5)
at App (http://localhost:3001/static/js/client.js:140292:5)
at ConnectFunction (http://localhost:3001/static/js/client.js:211057:68)
at injectIntl(Connect(App))
at ConnectFunction (http://localhost:3001/static/js/client.js:211057:68)
at Route (http://localhost:3001/static/js/client.js:216619:29)
at Switch (http://localhost:3001/static/js/client.js:216825:29)
at Route (http://localhost:3001/static/js/client.js:216619:29)
at AsyncConnect (http://localhost:3001/static/js/client.js:155869:5)
at AsyncConnectWithContext (http://localhost:3001/static/js/client.js:155988:7)
at C (http://localhost:3001/static/js/client.js:216880:37)
at ConnectFunction (http://localhost:3001/static/js/client.js:211057:68)
at ScrollToTop (http://localhost:3001/static/js/client.js:160884:1)
at ConnectFunction (http://localhost:3001/static/js/client.js:211057:68)
at Router (http://localhost:3001/static/js/client.js:216248:30)
at ConnectedRouter (http://localhost:3001/static/js/client.js:17296:7)
at ConnectedRouterWithContext (http://localhost:3001/static/js/client.js:17402:25)
at ConnectFunction (http://localhost:3001/static/js/client.js:211057:68)
at IntlProvider (http://localhost:3001/static/js/client.js:209153:9)
at ConnectFunction (http://localhost:3001/static/js/client.js:211057:68)
at Provider (http://localhost:3001/static/js/client.js:210771:20)
at CookiesProvider (http://localhost:3001/static/js/client.js:173588:28)

Here is my py file

# -*- coding: utf-8 -*-
from plone.app.textfield import RichText
from plone.autoform import directives
from plone import schema
from plone.dexterity.content import Container
from plone.supermodel.directives import fieldset

# from plone.namedfile import field as namedfile
from plone.supermodel import model
from plone.autoform import directives

# from plone.supermodel.directives import fieldset
# from z3c.form.browser.radio import RadioFieldWidget
# from zope import schema
from zope.interface import implementer

import json
from z3c.form.interfaces import IAddForm
import datetime

from zope.interface import provider
from zope.schema.interfaces import IContextAwareDefaultFactory
from zope.interface import invariant

# from budget.site import _

#notes of things to do on wednesday apr 17, 2024
#https://4.docs.plone.org/external/plone.app.dexterity/docs/advanced/event-handlers.html
# add an object created event to calculate the object title
#add an object ioobjectaddedevent to calculate deptcode and deptname and school and total
#total has to be calculated on modified as well
def meeting_date_default_value():
    return datetime.datetime.today() + datetime.timedelta(7)

def getdeptcode(deptcodename):
    print("got to getdeptcode")
    return deptcodename[0:5]

def getdeptinfo(deptcodename):
    print(deptcodename)
    deptcode=deptcodename[0:5]
    dlen=len(deptcodename)-1
    deptname=deptcodename[7:dlen]
    deptinfo={"deptcode":deptcode,"deptname":deptname}
    return deptinfo

@provider(IContextAwareDefaultFactory)
def get_container_id(context):
    return context.id.upper()

MIXEDFIELD_SCHEMA = json.dumps(
    {
        'type': 'object',
        'properties': {'items': {'type': 'array', 'items': {'type': 'object', 'properties': {}}}},
    }
)




class IFulltimestaff(model.Schema):
    """ Marker interface and Dexterity Python Schema for Fulltimestaff
    """
    # If you want, you can load a xml model created TTW here
    # and customize it in Python:

    # model.load('fulltimestaff.xml')

    # directives.widget(level=RadioFieldWidget)
    # level = schema.Choice(
    #     title=_(u'Sponsoring Level'),
    #     vocabulary=LevelVocabulary,
    #     required=False
    # )

    # text = RichText(
    #     title=_(u'Text'),
    #     required=False
    # )

    # url = schema.URI(
    #     title=_(u'Link'),
    #     required=False
    # )

    # fieldset('Images', fields=['logo', 'advertisement'])
    # logo = namedfile.NamedBlobImage(
    #     title=_(u'Logo'),
    #     required=False,
    # )

    # advertisement = namedfile.NamedBlobImage(
    #     title=_(u'Advertisement (Gold-sponsors and above)'),
    #     required=False,
    # )

    # directives.read_permission(notes='cmf.ManagePortal')
    # directives.write_permission(notes='cmf.ManagePortal')
    # notes = RichText(
    #     title=_(u'Secret Notes (only for site-admins)'),
    #     required=False
    # )
    # the object id assigned by plone
    directives.omitted(IAddForm, 'budgetid')  # will omit from dataentry form but show on editing form
    #directives.omitted('field_1', 'field_2')
    #directives.mode(additionalInfo='hidden') can be input, display or hidden  this sets mode
    budgetid=schema.TextLine(
        title="budget id",
        description="Object ID",
        # values=[2024-2025],
        required=False,
        defaultFactory=get_container_id,
    )
    directives.read_permission(fiscalyear='cmf.ModifyPortalContent')  #only entry user should be able to edit field.
    #so change appropriately once you set up users.
    #form.read_permission(secret="cmf.ManagePortal")
    fiscalyear = schema.Choice(
        title="Fiscalyear",
        description="Fiscal Year",
        vocabulary="budget.fiscalyearlist",
        #values=[2024-2025],
        required=False,
    )
    deptcodename = schema.Choice(
        title="Department Code and Name",
        description="Department",
        vocabulary="budget.budgetcodes",
        #values=["10101","10201"],
        required=False,
    )
    division = schema.TextLine(
        title="Division",
        description="Division",
        required=False,
    )
    directives.omitted(IAddForm, 'deptcode')  #will omit from dataentry form but show on editing form
    deptcode = schema.TextLine(
        title="Department Code",
        description="Department Code",
        required=False,
        #defaultFactory=getdeptcode(data.deptcodename),  # sets the default

    )

    deptname = schema.TextLine(
        title="Department Name",
        description="Department Name",
        max_length=200,
        required=False,
    )


    Total = schema.Float(
        title="Department Total",
        description="Department Total",
        required=False,
    )
    deptheadchairname = schema.TextLine(
        title="deptheadchair",
        description="Person that fills out form",
        max_length=200,
        required=False,
    )
    deptheadchairemail = schema.Email(
        title="deptheadchair email",
        description="Person that fills out form",
        required=False,
    )
    entrydate = schema.Datetime(
        title="deptheadchair entrydate",
        description="Person that fills out form",
        required=False,
        defaultFactory=meeting_date_default_value,  #sets the default date
    )
    deanvpname = schema.TextLine(
        title="Dean-VP Approver",
        description="Dean or VP name",
        max_length=200,
        required=False,
    )
    deanvpemail = schema.Email(
        title="Dean-VP Email",
        description="Dean or VP email",
        required=False,
    )
    deanvpdecision = schema.Choice(
        title="Dean-VP Decision",
        description="Dean or VP decision",
        values=["Approved", "Denied", "Pending"],
        required=False,
    )
    deanvpdecisiondate = schema.Datetime(
        title="Dean-VP Decision Date",
        description="Dean or VP Decision date",
        required=False,
    )
    deanvpnotes = schema.TextLine(
        title="Dean-VP Notes",
        description="Dean or VP Notes",
        max_length=2000,
        required=False,
    )
    presprovostname = schema.TextLine(
        title="President-Provost Approver",
        description="President or Provost",
        max_length=200,
        required=False,
    )
    presprovostemail = schema.Email(
        title="President-Provost Email",
        description="President or Provost email",
        required=False,
    )
    presprovostdecision = schema.Choice(
        title="President-Provost Decision",
        description="President or Provost Decision",
        values=["Approved", "Denied", "Pending"],
        required=False,
    )
    presprovostdecisiondate = schema.Datetime(
        title="President-Provost Decision Date",
        description="President or Provost decision date",
        required=False,
    )
    presprovostnotes = schema.TextLine(
        title="President-Provost Notes",
        description="President or Provost notes",
        max_length=2000,
        required=False,
    )
    budgetname = schema.TextLine(
        title="Budget Approver",
        description="Budget",
        max_length=200,
        required=False,
    )
    budgetemail = schema.Email(
        title="Budget Email",
        description="Budget email",
        required=False,
    )
    budgetdecision = schema.Choice(
        title="Budget Decision",
        description="Budget decision",
        values=["Approved", "Denied", "Pending"],
        required=False,
    )
    budgetdecisiondate = schema.Datetime(
        title="Budget Decision Date",
        description="Budget decision date",
        required=False,
    )
    budgetnotes = schema.TextLine(
        title="Budget Notes",
        description="Budget Notes",
        max_length=2000,
        required=False,
    )
    allocationcommname = schema.TextLine(
        title="Allocation Committee Approver",
        description="Committee",
        max_length=200,
        required=False,
    )
    allocationcommemail = schema.Email(
        title="Allocation Committee Email",
        description="Comittee email",
        required=False,
    )
    allocationcommdecision = schema.Choice(
        title="Allocation Committee Decision",
        description="Committee decision",
        values=["Approved", "Denied", "Pending"],
        required=False,
    )
    allocationcommdecisiondate = schema.Datetime(
        title="Allocation Committee Decision Date",
        description="Committee",
        required=False,
    )
    allocationcommnotes = schema.TextLine(
        title="Allocation Committee Notes",
        description="Committee",
        max_length=2000,
        required=False,
    )

    accountmname = schema.TextLine(
        title="Account Approver",
        description="Account",
        max_length=200,
        required=False,
    )
    accountmemail = schema.Email(
        title="Account Email",
        description="Account email",
        required=False,
    )
    accountmdecision = schema.Choice(
        title="Account Decision",
        description="Account Decision",
        values=["Approved", "Denied", "Pending"],
        required=False,
    )
    accountmdecisiondate = schema.Datetime(
        title="Account Decision Date",
        description="Account decision date",
        required=False,
    )
    accountmnotes = schema.TextLine(
        title="Account Notes",
        description="Account Notes",
        max_length=2000,
        required=False,
    )
    establishedmname = schema.TextLine(
        title="Established Approver",
        description="Established",
        max_length=200,
        required=False,
    )
    establishedmemail = schema.Email(
        title="Established Email",
        description="Established email",
        required=False,
    )
    establishedmdecision = schema.Choice(
        title="Established Decision",
        description="Established Decision",
        values=["Approved", "Denied", "Pending"],
        required=False,
    )
    establishedmdecisiondate = schema.Datetime(
        title="Established Decision Date",
        description="Established Decision date",
        required=False,
    )
    establishedmnotes = schema.TextLine(
        title="Established Notes",
        description="Established notes",
        max_length=2000,
        required=False,
    )


    fulltime_positions = schema.JSONField(
        title='Mixedfield: datagrid field for Plone',
        required=False,
        schema=MIXEDFIELD_SCHEMA,
        widget='faculty2widget',
        default={'items': []},
        missing_value={'items': []},
    )

    @invariant
    def validate(data):
        print("got to validate")
        if data.deptcodename is not None:
            print("got to validate")
            deptinfo=getdeptinfo(data.deptcodename)
            #data.deptcode=deptinfo["deptcode"]
            #data.deptname=deptinfo["deptname"]


@implementer(IFulltimestaff)
class Fulltimestaff(Container):
    """ Content-type class for IFulltimestaff
    """

Here is my View

import React from 'react';
import { DefaultView } from '@plone/volto/components';
import { Container } from 'semantic-ui-react';


const FacultyBudget = props => {
  const { content } = props;
  return (
    <>
      <DefaultView {...props} />
      <Container>
            <h1 className="documentFirstHeading">

            {content.title}
          </h1>
          {content.description && (
            <p className="documentDescription">{content.description}</p>
          )}
          {<Table celled className="Fulltime positions list">
            <Table.Header>
            <Table.Row>
                <Table.HeaderCell>Position</Table.HeaderCell>
                <Table.HeaderCell>Salary</Table.HeaderCell>
                <Table.HeaderCell>Justification</Table.HeaderCell>

            </Table.Row>
            </Table.Header>

            <Table.Body>
            {content?.items?.map((item) => (
                <Table.Row>

                <Table.Cell>{item.positiontype}</Table.Cell>
                <Table.Cell>{item.salary}</Table.Cell>
                <Table.Cell>{item.justification}</Table.Cell>
                </Table.Row>
            ))}
            </Table.Body>
        </Table>}
          <div dangerouslySetInnerHTML={{ __html: content.details.data }} />

      </Container>
    </>
  );
};


const FacultyBudgetView = ({ content }) => {
    return (
        <Container>
        <h2>Faculty View</h2>
        <h3>Faculty LIst</h3>
        <FacultyBudget mybudget={content.faculty2widget} />
        </Container>
    );
 };
export default FacultyBudgetView;

Here is my widget

import React from 'react';

import { ReactTableWidget } from '@eeacms/volto-react-table-widget';








const ItemSchema = () => ({
    title: 'Faculty-List',
    properties: {
        positiontype: {
            title: 'Position Type',
            type: 'string',

        },
        salary: {
            title: 'Salary',
            type:'number',
        },
        justification: {
            title: 'Justification',
            type: 'string',
        },
    },
    fieldsets: [
        {
            id: 'default',
            title: 'Faculty-List',
            fields: [
                'positiontype',
                'salary',
                'justification',
            ],
        },
    ],
    required: [],
});




const Faculty2Widget = (props) => {
  return (
    <ReactTableWidget
      schema={ItemSchema()}
      {...props}
      csvexport={false}
      csvimport={false}
      value={props.value?.items || props.default?.items || []}
      onChange={(id, value) => props.onChange(id, { items: value })}
    />
  );
};

export default Faculty2Widget;****strong text

What is the URL of the tutorial you are following?

The only URL you posted is to Plone 4 documentation, which is no longer supported. Note, we should slap a banner on that site as we did for Plone 3 and 5, or just take it down. @polyester who can do that?

The latest release of Plone is 6.0.10.

Hi @kamarjit, you are setting up a mixed field of 12. Content types: Reference – Mastering Plone 6 development — Plone Training 2024 documentation

Please post your code in a readable format.
See the editor buttons on how to post code.

and I am using the widget from here

That is from 2022. As the friendly banner at the top indicates, use the latest available training instead. Archived trainings are not supported.

This is where you should begin: