payloadcms/payload

Getting TypeError, cant read `req` from `initPageResult` when developing plugin

Closed this issue · 14 comments

Describe the Bug

I'm upgrading my plugin from a v2 plugin to a v3 plugin.

The following code gives me TypeError: Cannot read properties of undefined (reading 'req')

const AppointmentsList: React.FC<AdminViewProps> = ({
	initPageResult,
	params,
	searchParams,
}) => {
    // rest of code
    return (
		<DefaultTemplate
			i18n={initPageResult.req.i18n}
			locale={initPageResult.locale}
			params={params}
			payload={initPageResult.req.payload}
			permissions={initPageResult.permissions}
			searchParams={searchParams}
			user={initPageResult.req.user || undefined}
			visibleEntities={initPageResult.visibleEntities}
		>
			<div className="collection-list appointments-calendar-view">
				<h1>Appointments List</h1>
				// rest of code
			</div>
		</DefaultTemplate>
    );
};

Link to the code that reproduces this issue

https://github.com/ahmetskilinc/payload-appointments-plugin/tree/upgrade-to-3.0

Reproduction Steps

  1. clone repo
  2. $ cd /payload-appointments-plugin/dev
  3. $ pnpm dev
  4. navigate to localhost:3000
  5. should see error.

Which area(s) are affected? (Select all that apply)

plugin: other

Environment Info

React: 19.0.0-rc-3edc000d-20240926
Node: 20.12.0
pnpm: 9.12.1
payload: 3.0.0-beta.120

Same in .122

Looking at your code, you've got 'use client' at the very top. This component has to be a server component if you want access to req and the local API. If you need client side behaviour, you can include a client side component to handle it and pass it the props that you need.

Remove the use client but know that react hooks wont work in this component either, you'll need to move those down to a client component.

Also as of beta 121, custom views are public by default so remember to add your access control on the req.user.

Let me know if this fixes your issues here

@paulpopus thanks so much This worked. Added a client component for the relevant parts and passed in as props.

What do you mean by custom views are public? Oh you mean they are public without needing authentication/authorisation and need custom access control fort them?

you mean they are public without needing authentication/authorisation and need custom access control fort them?

Yep! To access control them you can just check the user on the req on the server side and then call notFound or a redirect depending on what you wanna do

closing this issue then!

I'm still getting initPageResults as undefined, I'm trying to create a custom list view Component. I don't have use client or anything, and using the AdminViewProps. Any idea what the issue is here? on the latest beta.

import React from 'react'
import { Gutter } from '@payloadcms/ui'
import { AdminViewProps } from 'payload'

export default async function MediaList(props: AdminViewProps) {
  const { initPageResult, params, searchParams } = props

  console.log('page result: ', initPageResult)

  return (
    <Gutter>
      <pre>{JSON.stringify(initPageResult, null, 2)}</pre>
    </Gutter>
  )
}

Only difference is that I'm defining a custom list view Component, this is how i defined it in my Media Collection;

admin: {
    components: {
      views: {
        list: {
          Component: 'src/components/payload/AdminViews/Media/List',
        },
      },
    },
  },

@ForrestDevs try to log what props are without a type and see what you get..

aha, well thats interesting, there's a whole load of things when a console.log(props), it appears that initPageResults is already destructured in the prop object.

@ForrestDevs My guess would be that in a List component has different prop type to an Admin view.

It appears that the payload src code still uses that same type for the props in the default ListView, not sure how this is working...
https://github.com/payloadcms/payload/blob/beta/packages/next/src/views/List/index.tsx

This is the result I get after directly logging the props:
note it has a similar structure to the AdminViewProps however it seems things like initPageResult have already been destructured.
cleaned up the output for brevity sake.

{
    collectionConfig: {
      access: {
        create: [Function: authenticated],
        delete: [Function: authenticated],
        read: [Function: anyone],
        unlock: [Function: __TURBOPACK__default__export__],
        update: [Function: authenticated]
      },
      admin: {
        components: [Object],
        custom: {},
        enableRichTextLink: true,
        enableRichTextRelationship: true,
        pagination: [Object],
        useAsTitle: 'filename'
      },
      auth: false,
      custom: {},
      endpoints: [],
      fields: [
        [Object], [Object],
        [Object], [Object],
        [Object], [Object],
        [Object], [Object],
        [Object], [Object],
        [Object], [Object],
        [Object], [Object]
      ],
      hooks: {
        afterChange: [],
        afterDelete: [Array],
        afterForgotPassword: [],
        afterLogin: [],
        afterLogout: [],
        afterMe: [],
        afterOperation: [],
        afterRead: [],
        afterRefresh: [],
        beforeChange: [Array],
        beforeDelete: [],
        beforeLogin: [],
        beforeOperation: [],
        beforeRead: [],
        beforeValidate: [],
        me: [],
        refresh: []
      },
      timestamps: true,
      upload: {
        imageSizes: [Array],
        focalPoint: false,
        crop: false,
        disableLocalStorage: true,
        adapter: 's3',
        handlers: [Array],
        bulkUpload: true,
        staticDir: 'media'
      },
      versions: false,
      slug: 'media',
      labels: { plural: 'Media', singular: 'Media' },
      joins: {}
    },
    collectionSlug: 'media',
    data: {
      docs: [],
      totalDocs: 43,
      limit: 25,
      totalPages: 2,
      page: 1,
      pagingCounter: 1,
      hasPrevPage: false,
      hasNextPage: true,
      prevPage: null,
      nextPage: 2
    },
    hasCreatePermission: true,
    i18n: {},
    limit: 25,
    listPreferences: { sort: null, limit: 25 },
    listSearchableFields: undefined,
    locale: undefined,
    newDocumentURL: '/admin/collections/media/create',
    params: { segments: [ 'collections', 'media' ] },
    payload: <ref *1> BasePayload {
      auth: [AsyncFunction: auth],
      authStrategies: [ [Object] ],
      collections: {},
      config: {},
      count: [AsyncFunction: count],
      create: [AsyncFunction: create],
      db: {},
      decrypt: [Function: decrypt],
      duplicate: [AsyncFunction: duplicate],
      email: {},
      encrypt: [Function: encrypt],
      extensions: undefined,
      find: [AsyncFunction: find],
      findByID: [AsyncFunction: findByID],
      findGlobal: [AsyncFunction: findGlobal],
      findGlobalVersionByID: [AsyncFunction: findGlobalVersionByID],
      findGlobalVersions: [AsyncFunction: findGlobalVersions],
      findVersionByID: [AsyncFunction: findVersionByID],
      findVersions: [AsyncFunction: findVersions],
      forgotPassword: [AsyncFunction: forgotPassword],
      getAdminURL: [Function: getAdminURL],
      getAPIURL: [Function: getAPIURL],
      globals: { config: [Array] },
      importMap: {},
      jobs: { queue: [AsyncFunction: queue], run: [AsyncFunction: run] },
      logger: {},
      login: [AsyncFunction: login],
      resetPassword: [AsyncFunction: resetPassword],
      restoreGlobalVersion: [AsyncFunction: restoreGlobalVersion],
      restoreVersion: [AsyncFunction: restoreVersion],
      schema: undefined,
      secret: '',
      sendEmail: [AsyncFunction: sendEmail],
      types: undefined,
      unlock: [AsyncFunction: unlock],
      updateGlobal: [AsyncFunction: updateGlobal],
      validationRules: undefined,
      verifyEmail: [AsyncFunction: verifyEmail],
      versions: {}
    },
    permissions: {
      canAccessAdmin: true,
      collections: {},
      globals: {},
    },
    searchParams: { limit: '25' },
    user: {}
  }

Update: after reviewing the default list view more closely I realized that because I was defining a Component rather than a Whole View that the props being passed were from the createMappedComponent function below.

https://github.com/payloadcms/payload/blob/beta/packages/next/src/views/List/index.tsx

const createMappedComponent = getCreateMappedComponent({
      importMap: payload.importMap,
      serverProps: {
        collectionConfig,
        collectionSlug,
        data,
        hasCreatePermission: permissions?.collections?.[collectionSlug]?.create?.permission,
        i18n,
        limit,
        listPreferences,
        listSearchableFields: collectionConfig.admin.listSearchableFields,
        locale: fullLocale,
        newDocumentURL: formatAdminURL({
          adminRoute,
          path: `/collections/${collectionSlug}/create`,
        }),
        params,
        payload,
        permissions,
        searchParams,
        user,
      },
    })

@ahmetskilinc Thanks for your help!

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