signals icon indicating copy to clipboard operation
signals copied to clipboard

Cannot set property createElement of [object Module] which has only a getter

Open Draecal opened this issue 2 years ago • 17 comments
trafficstars

Error: Cannot set property createElement of [object Module] which has only a getter

Environment:

  • Nextjs14
  • AppRouter
  • src folder
  • Tailwindcss

Code:

import { GeistSans, GeistMono } from 'geist/font'
import { signal } from '@preact/signals-react'
import './globals.css'

export const toOpen = signal(false)
export const openDialog = () => { toOpen.value = true ; console.log(toOpen)}

export const Register = () => {
  return (<>
    <dialog open={toOpen.value}>
      <p>Create a new account</p>
    </dialog>
  </>
  )
}

standard boilerplate code follows

Expected:

At least the app to load, instead receiving the error described.

Tried all approaches possible, even the documentation examples within my environment.

Draecal avatar Nov 01 '23 18:11 Draecal

I am having the similar error. The error on the terminal says "cannot set property of jsx". Anything new?

ghost2023 avatar Nov 03 '23 16:11 ghost2023

I am running into the error as well when I try to use signals in a Nextjs13 project. When 'reactStrictMode' is set to false, signal seems able to continue working even with the error. Not sure if it is Next specific, but the issue was not observed in React projects.

d-zheng avatar Nov 03 '23 21:11 d-zheng

Spent a bit time on it today, and found out the error likely related to Next.js's pre-rendering feature (https://nextjs.org/learn-pages-router/basics/data-fetching/pre-rendering). Pre-rendering happens at build time (for production) or at each page request (for development). I've confirmed the error doesn't show up at build time if I replace 'next build' with 'next experimental-compile' which is a new command that skips the pre-rendering step. I didn't see this error either when I took dynamic import approach with { ssr : false } option for a component including signals.

d-zheng avatar Nov 04 '23 05:11 d-zheng

So basically don't use signals in nextjs. Sad

ghost2023 avatar Nov 04 '23 05:11 ghost2023

I don't think there's any need for signals to hook into React for SSR, since the state will never change server-side. Therefore, it should be enough (🤞) for signals-react to not call installJSXHooks: https://github.com/preactjs/signals/blob/main/packages/react/runtime/src/auto.ts#L339

Unfortunately, I don't know enough about either Signal's or Next's codebase to make that happen.

To help people googling find this issue, here's my error: TypeError: Cannot set property jsx of [object Module] which has only a getter

Related: vercel/next.js/issues/45054

Added a workaround to that issue.

JonAbrams avatar Nov 05 '23 06:11 JonAbrams

Thanks. I tried the workaround and it does the job. But I am seeing a side-effect that it voids the rendering optimization features of Signals, i.e., those components that are not subscribing to a signal would be also re-rendered when the signal value changes 😞

d-zheng avatar Nov 06 '23 01:11 d-zheng

Try the library I just published on npm (signals-react-safe), it fixes the re-rendering issue.

JonAbrams avatar Nov 06 '23 05:11 JonAbrams

@JonAbrams, thanks for the fix, it was quick.👍 Yeah, it is working most of the part now: the original error is gone ✔️, and only text node is updated when signal updates without re-rendering the component✔️. The only remaining one is when signal passed into a component as prop, the component doesn't get re-rendered (if referring to signal value in text node) when signal updates; referring to signal works though. Sorry, I am new to React/Next/Signals, maybe I missed anything? Anyway, the library with the fix provides pretty much everything of Signals I need for Next. Thanks again!

d-zheng avatar Nov 06 '23 15:11 d-zheng

To have the component re-render when a signal updates, use the useSignalValue hook I added.

JonAbrams avatar Nov 06 '23 16:11 JonAbrams

Works like a charm! 👍

d-zheng avatar Nov 06 '23 17:11 d-zheng

Or just add "use client" at the top of the file that imports the signal.

zeropaper avatar Nov 16 '23 14:11 zeropaper

To have the component re-render when a signal updates, use the useSignalValue hook I added.

I tried to use this in a nextjs page without luck. The signal I want to use is to hold the current lang value

// useI18n.tsx
import { signal, useSignalValue } from 'signals-react-safe'

export const language = signal<Languages>('en') // I export a signal here

// the translate fn just return a json object

export const useI18n = () => {
  return translate(languages[useSignalValue(language)])
}
// app/events/page.tsx

import React from 'react'
import { useI18n } from '@/app/ui/hooks/useI18n'

const Page = () => {
    const t = useI18n()

    return (
        <section>
             {t('CREATE_EVENT')}
        </section>
    )
}

export default Page

The error when this page renders on the server:

 node_modules/signals-react-safe/dist/index.mjs (15:37) @ signal2
 ⨯ useState only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more: https://nextjs.org/docs/messages/react-client-hook-in-server-component
    at useI18n (./app/ui/hooks/useI18n.tsx:28:98)
    at Page (./app/events/page.tsx:18:77)
    at stringify (<anonymous>)

hdwv avatar Nov 17 '23 20:11 hdwv

Under the hood useSignalValue uses useState. So it means you can only use it in client components. I should look into if it's possible to avoid using useState in server components to fix this.

JonAbrams avatar Nov 17 '23 21:11 JonAbrams

Actually, it turns out you're not supposed to use hooks in server components. Instead, access the signal and/or it's value directly in the server component. Since server components don't re-render, it should be safe.

In the future, please raise an issue with the signals-react-safe library here.

e.g.

import { language } from '../mySignals';

const Page = () => {
    const t = translate(languages[language.value]);

    return (
        <section>
             {t('CREATE_EVENT')}
        </section>
    )
}

JonAbrams avatar Nov 17 '23 22:11 JonAbrams

it seems like you guys seeing this error at the server side component, but I use it literally at client side components but not with the props drilling way, just the export and import way, and the error occurs...

QingjiaTsang avatar Nov 29 '23 02:11 QingjiaTsang

The error appears to stem from trying to patch React.createElement.

https://github.com/preactjs/signals/blob/d7f2afafd7ce0f914cf13d02f87f21ab0c26a74b/packages/react/runtime/src/auto.ts#L371

There seems to be some inconsistent behavior on webpack module resolution with ES module interop in how import React from 'react' or import * as React from 'react' is converted to require('react') calls. The same is true for the runtime JSX module patching.

We've noticed our patching of React.createElement started failing with the same error shown in original post in next@14. I managed to workaround that by patching like

(React.default || React).createElement = wrap(React.createElement);

or

Object.assign(React.default || React, {createElement: wrap(React.createElement)});

See https://github.com/lit/lit/pull/4575/files#diff-c058cf28cfeb3924265a8702a1c831cc7b6412b860c404754e9f99791287e797

augustjk avatar Mar 09 '24 01:03 augustjk

This is a problem related with monkeypatching from @preact/signals-react versions 1.x.x. Now you can use babel plugin (but not all feature of next.js working with it). Alternatively - you can @preact-signals/safe-react with swc plugin

XantreDev avatar Mar 10 '24 16:03 XantreDev