prevwong / craft.js

🚀 A React Framework for building extensible drag and drop page editors
https://craft.js.org
MIT License
7.4k stars 715 forks source link

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

Open trulysinclair opened 2 years ago

trulysinclair commented 2 years ago

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?

prevwong commented 2 years ago

Where're you using this component?

trulysinclair commented 2 years ago

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

prevwong commented 2 years ago

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

wuichen commented 2 years ago

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

tsaunde123 commented 1 year ago

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

WilsonLe commented 1 year ago

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 that wraps around 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

akucinskii commented 12 months ago

Any update on this?

aaliboyev commented 6 months ago

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

danielbattat commented 6 months ago

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

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

wuichen commented 5 months ago

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

WilsonLe commented 5 months ago

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.

Yhozen commented 5 months ago

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 commented 5 months ago

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

benhexie commented 5 months ago

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

Yhozen commented 5 months ago

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

This btw doesn't work

benhexie commented 5 months ago
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

CarlosLukass commented 2 months ago

You just need to add the "Element" Component into your Resolver @benhexie, that is how I fix the issue. :)