craft.js icon indicating copy to clipboard operation
craft.js copied to clipboard

Conditionally wrapping `Element` causes an error, works without wrapping

Open trulysinclair opened this issue 2 years ago • 17 comments

I'm trying to conditionally render either an Elemen or the actual dom node based on whether or not I'm editing or exporting. Currently my own solution for exporting the nodes to Next.js or HTML, otherwise it requires two separate copies of the same node, one for editing and one for exporting which I'm trying to avoid.

Copying the same Element implementation you have, I try to hand down the exact same information.

let isExporting = false;

export type Element<T extends React.ElementType> = {
  id?: NodeId;
  is?: T;
  custom?: Record<string, any>;
  children?: React.ReactNode;
  canvas?: boolean;
} & React.ComponentProps<T>;

export function Element<T extends React.ElementType>({
  id,
  children,
  ...props
}: Element<T>) {

  // const testElement1 = React.createElement(
  //   CraftElement,
  //   { id, is: "div", custom: props.custom, canvas: props.canvas },
  //   children
  // );

  const testElement2 = (
    <CraftElement id={id} {...props}>
      {children}
    </CraftElement>
  );

  return (isExporting
    ? (React.createElement(props.is, props.elementProps, children))
    : testElement2);

  // return (
  //   <CraftElement id={id} {...props}>
  //     {children}
  //   </CraftElement>
  // );
}

This actually works, but only as long as there are no children Elements, as having children causes

Error: Invariant failed: The component type specified for this node (Element) does not exist in the resolver

I've tried various implementations, all failing: directly supply the props, change the is to div to see if it's a scope issue, etc.

Is there any way I can get this to work?

trulysinclair avatar Feb 28 '22 20:02 trulysinclair

Where're you using this <Element /> component?

prevwong avatar Mar 07 '22 15:03 prevwong

@prevwong I'm using it inside of a custom Element implementation. So if I'm exporting it uses React.createElement(), else <Element id={id} is={is} {...elementProps}>{children}</Element>

And otherwise it's an exact placement where you would use Element inside a user component.

And it works fine with text as children but not elements or components.

trulysinclair avatar Mar 07 '22 16:03 trulysinclair

Weird, would it be possible for you to create a codesandbox reproducing the behaviour?

prevwong avatar Mar 08 '22 13:03 prevwong

Hi I can confirm this error is still happening. It happens to me when I have < element >{children}< /element >

wuichen avatar Apr 29 '22 14:04 wuichen

Has anyone managed to solve this? I appear to be having the same issue.

This is my user component that make use of the same Element component as @xsmithdev

import React from "react";
import { Text } from "./Text";
import { Button } from "./Button";
import { Container } from "./Container";
import { Element } from "components/Element";

interface PromptProps {
  background?: string;
  padding?: number;
}

export const Prompt = ({ background = "#fff", padding = 20 }: PromptProps) => {
  return (
    <Element
      id="prompt-canvas"
      is={Container}
      background={background}
      padding={padding}
      canvas
    >
      <div className="text-only">
        <Text text="Title" fontSize={20} />
        <Text text="Subtitle" fontSize={15} />
      </div>
      <div className="buttons-only">
        <Button size="small" variant="contained" color="primary">
          Learn more
        </Button>
      </div>
    </Element>
  );
};

But it gives me the following error Error: Invariant failed: The component type specified for this node (fe) does not exist in the resolver

tsaunde123 avatar Oct 01 '22 12:10 tsaunde123

Weird, would it be possible for you to create a codesandbox reproducing the behaviour?

I went ahead and reproduce the behavior. Basically I'm attempting to create a custom <Element/> that wraps around <CraftJSElement/> that takes an extra prop isSSR. If the <Element isSSR={true}/>, the element return a React element (rather than returning a CraftJSElement/>).

https://codesandbox.io/s/craftjs-cond-element-9szcsl

WilsonLe avatar Dec 15 '22 18:12 WilsonLe

Any update on this?

akucinskii avatar Aug 01 '23 13:08 akucinskii

This issue needs attention. When you use Element inside custom component it throws error.

aaliboyev avatar Jan 24 '24 09:01 aaliboyev

I fixed this by ensuring my Container was listed in the resolver list.

<Editor resolver={{Button, Text, Container}}> .... </Editor>

danielbattat avatar Jan 24 '24 12:01 danielbattat

I fixed this by ensuring my Container was listed in the resolver list.

<Editor resolver={{Button, Text, Container}}> .... </Editor>

why would this resolve the issue

wuichen avatar Feb 11 '24 09:02 wuichen

I fixed this by ensuring my Container was listed in the resolver list.

<Editor resolver={{Button, Text, Container}}> .... </Editor>

The reproduce code sandbox I created above (https://codesandbox.io/s/craftjs-cond-element-9szcsl) clearly has Container in the Resolver list and yet the issue persists.

WilsonLe avatar Feb 11 '24 15:02 WilsonLe

The issue is about craft.js not resolving the default "div" I got the following error

image

and looking the dist folder from node_modules I was able to confirm that it was the div image Also the source code indicates that div is the default "is" value

So I finally fixed it by creating a dumb Div component

import type { ComponentProps } from 'react'
import { type Node, useNode } from '@craftjs/core'

export const Div = (
  props: ComponentProps<'div'>,
) => {
  const {
    connectors: { connect, drag },
  } = useNode()

  return <div {...props} ref={ref => connect(drag(ref!))} />
}

Div.craft = {
  rules: {
    canDrag: (node: Node) => node.data.props.text !== 'Drag',
  },
}

then I passed the Div component to the editor component

      <Editor resolver={{ Card, Button, Text, Container, Div }}>

and finally where I was using Element without a "is" prop I changed it to

   <Element is={Div} id="text" canvas>

I hope this is useful for someone

Yhozen avatar Feb 20 '24 01:02 Yhozen

@Yhozen Dammnnnn!.... This is God-tier debugging! 🙌 I had to follow immediately.

benhexie avatar Feb 20 '24 17:02 benhexie

This btw doesn't work

The issue is about craft.js not resolving the default "div" I got the following error

image

and looking the dist folder from node_modules I was able to confirm that it was the div image Also the source code indicates that div is the default "is" value

So I finally fixed it by creating a dumb Div component

import type { ComponentProps } from 'react'
import { type Node, useNode } from '@craftjs/core'

export const Div = (
  props: ComponentProps<'div'>,
) => {
  const {
    connectors: { connect, drag },
  } = useNode()

  return <div {...props} ref={ref => connect(drag(ref!))} />
}

Div.craft = {
  rules: {
    canDrag: (node: Node) => node.data.props.text !== 'Drag',
  },
}

then I passed the Div component to the editor component

      <Editor resolver={{ Card, Button, Text, Container, Div }}>

and finally where I was using Element without a "is" prop I changed it to

   <Element is={Div} id="text" canvas>

I hope this is useful for someone

benhexie avatar Feb 20 '24 19:02 benhexie

What kind of error are you getting? It is working fine for me now

This btw doesn't work

Yhozen avatar Feb 20 '24 19:02 Yhozen

Invariant failed: The component type specified for this node (ke2) does not exist in the resolver. 

I've been testing on WilsonLe's sandbox to no avail. You could try working on the sandbox and share a link to yours if you're successful. It would be very helpful 🙏

What kind of error are you getting? It is working fine for me now

This btw doesn't work

benhexie avatar Feb 20 '24 22:02 benhexie