Fable Bindings for react-beautiful-dnd throwing error building Droppable

330 views Asked by At

I'm trying to write Fable binders for the react-beautiful-dnd library, and I'm stuck on how to bind the Droppable component because it requires a function to create the children rather than have the children supplied directly like most other components.

I'm referencing this react example, where the component looks like this (see line 70 on that link):

render() {
    return (
      <DragDropContext onDragEnd={this.onDragEnd}>
        <Droppable droppableId="droppable">
          {(provided, snapshot) => (
            <div
              {...provided.droppableProps}
              ref={provided.innerRef}
              style={getListStyle(snapshot.isDraggingOver)}
            >
              {this.state.items.map((item, index) => (
                 <Draggable key={item.id} draggableId={item.id} index={index}>
                  {(provided, snapshot) => (
                    <div
                      ref={provided.innerRef}
                      {...provided.draggableProps}
                      {...provided.dragHandleProps}
                      style={getItemStyle(
                        snapshot.isDragging,
                        provided.draggableProps.style
                      )}
                    >
                      {item.content}
                    </div>
                  )}
                </Draggable>
              ))}
              {provided.placeholder}
            </div>
          )}
        </Droppable>
      </DragDropContext>
    );

So instead of just providing children directly, I need to provide a function of (DroppableProvided, DroppableStateSnapshot) -> ReactElement. Here is what I have so far:


type DroppableStateSnapshot = {
    isDraggingOver : bool
}

type DroppableProvided = {
    innerRef : Browser.Types.Element -> unit
}

type IDroppableAttribute = interface end
type droppable =
    static member inline droppableId (id : string) = unbox<IDroppableAttribute>("droppableId", id)
    static member inline childrenFn (f : (DroppableProvided * DroppableStateSnapshot) -> ReactElement) =
        unbox<IDroppableAttribute>("children", f)

[<Erase>]
type Droppable() =
    static member inline droppable (id : string) props (childrenFn: (DroppableProvided * DroppableStateSnapshot) -> ReactElement) =
        let _id = droppable.droppableId id
        let f = droppable.childrenFn childrenFn
        let props = keyValueList CaseRules.LowerFirst (_id :: (f :: props))
        ofImport "Droppable" "react-beautiful-dnd"  props []

I directly created the snapshot and provided types instead of trying to import them because I'm pretty sure I just need something with the right properties as the arguments to the childrenFn function.

Finally, here is a component using the droppable component I defined, providing a function to create the child element:

[<ReactComponent>]
let TestDroppable() =
  Droppable.droppable "test-droppable" [] (fun (provided, snapshot) ->
    div [ Ref provided.innerRef ] [
      BuildDraggable 0 [p [] [ str "This is the first item"]]
      BuildDraggable 1 [p [] [ str "This is the second item"]]
      BuildDraggable 2 [p [] [ str "This is the third item"]]
      BuildDraggable 3 [p [] [ str "This is the fourth item"]]
    ])

When I run this in the browser, I get an error of Uncaught TypeError: provided is undefined, pointing to the line where I set the Ref property. I'm doing this because the docs say you have to provide a ref value (see the "Children Function" section of the Droppable docs I linked). The stack trace shows this function being invoked from react-beautiful-dnd, so I can at least be reasonably sure I'm loading & executing the library.

So it seems like the function is being invoked by the framework correctly, but with null arguments. I don't know if this error is because of binding wrong, building the component wrong, not importing the right things, or some other combination of errors when working with the imported library.

The full source code is available here. I am putting this whole thing inside a DragDropContext, which seems to bind without issue.

2

There are 2 answers

1
Brian Berns On

I don't have any Fable/React experience, so I could be way off, but this F# code looks suspect to me:

type IDroppableAttribute = interface end

unbox<IDroppableAttribute>("children", f)

In a normal runtime environment, this will throw an InvalidCastException, because the Tuple type doesn't implement IDroppableAttribute. I don't know what happens when it's translated to Javascript instead, but I suspect it won't work there either.

0
rfrerebe On

What you are seeing in JavaScript is a render props.

You should simply include in a new prop named render matching your signature: childrenFn: (DroppableProvided * DroppableStateSnapshot) -> ReactElement

I haven't try it but it's usually this way. Also I advise reading the react website as it helps understand what's happening on the other side of the fence when using fable.