Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uncaught Error: Invariant failed: You can only use useNode in the context of <Editor />. #658

Open
rafark opened this issue Jun 10, 2024 · 11 comments

Comments

@rafark
Copy link

rafark commented Jun 10, 2024

Describe the bug
I've wrapped my entire app in an Editor component and I'm still getting this error when using useNode() in a "related" settings component. It works fine when using it in a UserComponent, but everytime i want to useNode inside a related settings component i get an error.

const root = ReactDOM.createRoot(
  document.getElementById('automated-emails-dashboard') as HTMLElement
);

root.render(
  <React.StrictMode>
    <Provider store={store}>
        <Editor resolver={namedWatchedComponents}>
            <DevTools />
            <App />
        </Editor>
    </Provider>
  </React.StrictMode>
);
export const TextSource: TextComponent & UserComponent<TextProps> = ({text}) => {
    const {connectors: {connect, drag}} = useNode()

    return <Text craftsRef={(ref: HTMLElement) => connect(drag(ref))} text={text}/>
}

TextSource.craft = {
    related: {
        settings: ComponentPreferences
    }
}

ComponentPreferences.tsx in another directory:

const ComponentPreferences: FC<{}> = () => {
    const node = useNode()
    return <div>
                The settings here
           </div>;
}

Uncaught Error: Invariant failed: You can only use useNode in the context of <Editor />.

Please only use useNode in components that are children of the <Editor /> component.
at invariant (tiny-invariant.js:12)
at ye (index.js:1)
at me (index.js:1)
at ComponentPreferences (ComponentPreferences.tsx:11)
at renderWithHooks (react-dom.development.js:16305)
at mountIndeterminateComponent (react-dom.development.js:20074)
at beginWork (react-dom.development.js:21587)
at beginWork$1 (react-dom.development.js:27426)
at performUnitOfWork (react-dom.development.js:26557)
at workLoopSync (react-dom.development.js:26466)
invariant @ tiny-invariant.js:12
ye @ index.js:1
me @ index.js:1
ComponentPreferences @ ComponentPreferences.tsx:11
renderWithHooks @ react-dom.development.js:16305
mountIndeterminateComponent @ react-dom.development.js:20074
beginWork @ react-dom.development.js:21587
beginWork$1 @ react-dom.development.js:27426
performUnitOfWork @ react-dom.development.js:26557
workLoopSync @ react-dom.development.js:26466
renderRootSync @ react-dom.development.js:26434
recoverFromConcurrentError @ react-dom.development.js:25850
performConcurrentWorkOnRoot @ react-dom.development.js:25750
workLoop @ scheduler.development.js:266
flushWork @ scheduler.development.js:239
performWorkUntilDeadline @ scheduler.development.js:533

@rafark
Copy link
Author

rafark commented Jun 10, 2024

useEditor() works though

@rafark
Copy link
Author

rafark commented Jun 10, 2024

When I log the editor I get inContext: true, so it IS inside the context. Why can't i use useNode() then?

@prevwong
Copy link
Owner

Seems like the error is coming from your ComponentPreferences component. Are you rendering the ComponentPreferences inside the Editor component?

@rafark
Copy link
Author

rafark commented Jun 11, 2024

Seems like the error is coming from your ComponentPreferences component. Are you rendering the ComponentPreferences inside the Editor component?

Thanks for taking the time to reply!

Yes, I put the Editor component at the root of the tree:

<React.StrictMode>
    <Provider store={store}>
        <Editor resolver={namedWatchedComponents}>
            <DevTools />
            <App />
        </Editor>
    </Provider>
  </React.StrictMode>

As i mentioned above, the Editor property inContext returns true when suing useEditor. I just can't use useNode for some reason.

If anyone else in the future is having this issue, I fixed it by writing a custom hook (useNodeProps()):

import { useEditor, useEditorReturnType } from "@craftjs/core"

type DefaultReturnData = {
    selectedId: string
}

export const useNodeProps = <S>(props: string[]) : useEditorReturnType<S & DefaultReturnData> => {
    // @ts-ignore
    return useEditor<S & DefaultReturnData>((state, query) => {
        const [selectedId] = state.events.selected
        const node = query.node(selectedId)
        const extractedProps: {[prop: string]: unknown} = {}

        props.forEach(prop => {
            extractedProps[prop] = node?.get()?.data?.props[prop] ?? ''
        })

        return {
            selectedId,
            ...extractedProps
        }
    })
}

@gaurav-simpplr
Copy link

I'm also facing this issue. Can writing a custom hook as suggested by @rafark is the only way ? I can use the useEditor hook, but useNode gives me error. Can you please help @prevwong

@dacapoo
Copy link

dacapoo commented Jun 23, 2024

Having the same problem. Using useNode outside Frame and inside Editor gives the same error.
@rafark is this intentional ?

@prevwong
Copy link
Owner

This shouldn't happen and I can't reproduce this so far - I've created a sandbox here with Related components: https://stackblitz.com/edit/stackblitz-starters-hddnpk?file=components%2FText.tsx

You can only use useNode within React components corresponding to Craft Nodes.

@dacapoo
Copy link

dacapoo commented Jun 24, 2024

@prevwong this is how I wanted to structure my app. And as you can see, this returns an error:
https://stackblitz.com/edit/stackblitz-starters-nkb7az?file=app%2Fpage.tsx

Do I have always to put the related component inside the corresponding node component file ?

@hugominas
Copy link

hugominas commented Jun 24, 2024

        <Frame>
          <Element canvas is="div">
            <Text />
          </Element>
          <TextSettings />
        </Frame>

If you add inside the Frame there is no error. I think it makes sense to have useNode scoped within the Frame element, don't you think?

@prevwong
Copy link
Owner

prevwong commented Jun 24, 2024

Yes, the TextSettings component in your example must be defined in the corresponding component's .craft.related settings (like in the original example I wrote above) if you want to use useNode() - this is because when you define it as a related component, then it will share the same Node context as the actual User component.

Otherwise, you can always just get the Node id of the selected node, and then just show the Text settings component if the selected component is a Text node:

const MySettingsPanel = () => {
   const { selectedType, selectedNodeId } = useEditor(state => {
       return {
          selectedType: [...state.events.selected][0]?.data.type
          selectedNodeId: [...state.events.selected][0]?.id,
       } 
   });
   
   if ( selectedType === 'Text' ) {
       return <TextSettings nodeId={selectedNodeId} />
   }
}

@dacapoo
Copy link

dacapoo commented Jun 24, 2024

Ah I see. thanks for your help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants