preactjs / signals

Manage state with style in every framework
https://preactjs.com/blog/introducing-signals/
MIT License
3.81k stars 95 forks source link

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

Open Draecal opened 1 year ago

Draecal commented 1 year ago

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

Environment:

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.

ghost2023 commented 1 year ago

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

d-zheng commented 1 year ago

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 commented 1 year ago

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.

ghost2023 commented 1 year ago

So basically don't use signals in nextjs. Sad

JonAbrams commented 1 year ago

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.

d-zheng commented 1 year ago

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 😞

JonAbrams commented 1 year ago

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

d-zheng commented 1 year ago

@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!

JonAbrams commented 1 year ago

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

d-zheng commented 1 year ago

Works like a charm! 👍

zeropaper commented 1 year ago

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

hdwv commented 1 year ago

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>)
JonAbrams commented 1 year ago

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 commented 1 year ago

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>
    )
}
QingjiaTsang commented 12 months ago

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...

augustjk commented 8 months ago

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

XantreDev commented 8 months ago

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