prevwong / craft.js

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

Customize props prior to adding a new node #139

Open Spenc84 opened 4 years ago

Spenc84 commented 4 years ago

Is your feature request related to a problem? Please describe. In all of the docs and usage examples that I've looked through so far there doesn't appear to be any way for a user to customize a component's props prior to adding it to the screen. In each case, it appears that the user would first need to add a component to the screen by dragging it to the desired location, and then click on it to customize the properties of that newly added component. This becomes problematic if the user needs to add something like 15 Row elements to the screen that all have the same slightly adjusted style.

Describe the solution you'd like Currently if you want to utilize Craft's 'drag to create' functionality the docs seem to recommend something like this:

const { connectors: { create }} = useEditor();
return <button ref={ref => create( ref, <Element prop1 prop2 propN />}>Drag Me</button>;

Unfortunately this limits you to a static set of props on the user components that are created from the drag handlers. In order for Craft to work for us though we will need an efficient way to dynamically set the props prior to the creation of each user component.

Ideally we'd like to be able to do something like this:

return <button ref={ref => create( ref, getComponent )}>Drag Me</button>;

... where getComponent is a callback function that is called whenever a drag operation is completed and dynamically returns the Component that should be created.

Additional context Right now we're putting a key property on the element that the drag handlers are being attached to and simply updating the key whenever a change is made to the properties that the new UserComponent should be created with. This causes React to replace that dom node with a new one which in turn fires off the ref's callback function allowing a new drag handler to be attached to the new dom node with the correctly updated properties for the UserComponent that will get created. While this works, it's not ideal. Continually replacing the dom node and reattaching new drag handlers can get expensive when multiple changes are made in rapid succession, like when updating padding with a slider.

linxianxi commented 3 years ago

same issue

dbousamra commented 3 years ago

For anyone else looking for a solution, I did what @Spenc84 suggested, and forced a remount using the key:

export const DraggableComponent = (props: { component: ToolboxComponent }) => {
  const { connectors } = useEditor();
  const { component } = props;
  const [key, setKey] = React.useState(uuid.v4());

  return (
    <Button
      key={key}
      size="sm"
      leftIcon={component.icon}
      variant="outline"
      w="100%"
      onDragEnd={() => setKey(uuid.v4())}
      ref={(ref) => component.create(connectors, ref)}
    >
      {component.name}
    </Button>
  );
};

Changing the key of a component forces a rerender.

nickm89 commented 2 years ago

I have a similar problem, need to pass props for position offsets but am unable to do so.

firling commented 1 year ago

Hello,

I have similar problem. I have a home made context (useEditorContext) that is continually updating its mouseCoords value, and i want to pass this value as a prop to my component. My problem is that the values passed are not the same as those of my context Heres my toolbox code:

const Toolbox = () => {
  const { mouseCoords, height, width } = useEditorContext();
  const { connectors: {create} } = useEditor();
  const [key, setKey] = useState(uuidv4())

  const x = Math.round(mouseCoords.x/(width/1920))
  const y = Math.round(mouseCoords.y/(height/1080))

  useEffect(() => {
    setKey(uuidv4())
  }, [mouseCoords])

  return (
    <Paper sx={{textAlign: "center"}} p="lg">
      <Title order={3}>Toolbox</Title>
      <Text mb="xs" color="dimmed">Drag to add</Text>

      <Button key={key} fullWidth variant="outline" mb="xs" ref={(ref) => create(ref, <TextElt x={x} y={y} />)}>
          Text
      </Button>
    </Paper>
  );
};

As you can see i tried to forcing a remount using the key but that doesnt fix my problem.

Edit : I found a solution to my problem, by using the onCreate callback

<Button fullWidth variant="outline" mb="xs" ref={(ref) => create(ref, <TextElt />, { onCreate: (nodeTree) => {
  const key = Object.keys(nodeTree.nodes)[0]
  setProp(key, props  => {
    props.x = x
    props.y = y
  })
}})}>
    Text
</Button>