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

Document process of using a third party react component #84

Open
danyx23 opened this issue Apr 15, 2018 · 16 comments
Open

Document process of using a third party react component #84

danyx23 opened this issue Apr 15, 2018 · 16 comments

Comments

@danyx23
Copy link
Contributor

danyx23 commented Apr 15, 2018

I think one of the big advantages of using Fable and React over e.g. Elm is that it is possible to use an ocean full of ready made React components. However, it is somewhat unclear what needs to be done to declare the required props etc. to use a third party React component. It would be great if this repo in the readme or the Fable docs could have a section on how to do this.

I don't know what the potential issues are with various kinds of e.g. Higher Order Components etc but it might be worth spelling these out explicitly as well. Please let me know if there is anything I can help with in particular to bring this forward.

@alfonsogarciacaro
Copy link
Member

alfonsogarciacaro commented Apr 15, 2018

Hi @danyx23! You're right, we have the pieces to easily import React components but we're missing the guide to do it. There are currently two ways to do it:

  • Dynamically: Just import the component and define the properties with a pojo. A mini-guide to do that can be found here.
  • Creating bindings: If you plan to use the component a lot, it maybe worthy to write some bindings to use DUs for the props and get more idiomatic F#. We're missing a guide for it (maybe @Zaid-Ajaj was writing one) although you can check other bindings in this repo (like the one for Leaflet). The most important thing is to call keyValueList before passing the prop list to the imported component (as in here).

EDIT: @danyx23 documented how to use 3rd party React components. You can also see below an example of importing HOC components.

@danyx23
Copy link
Contributor Author

danyx23 commented Apr 16, 2018

Hi @alfonsogarciacaro, thank your for your reply! How do you suggest to go forward with this? Should I create a PR with an initial draft of a documentation document in the docs folder of this repo that outlines how to use a third party react component?

I am relatively new to Fable and have only used React a little bit - are there areas that are more tricky, like HOCs or similar somewhat advanced features of React?

@alfonsogarciacaro
Copy link
Member

alfonsogarciacaro commented Apr 17, 2018

This is funny, I was about to write that I hadn't used React HOCs so I didn't know, but just today I had to use them in my project and realized we're missing a helper in fable-react. I think we could add something like this:

open Fable.Core
open Fable.Import.React
open Fable.Helpers.React
open Fable.Helpers.React.Props

// Helper to be added to fable-react
let importHigherOrderComponent<[<Pojo>]'P> (importMember: string) (importPath: string) (fn: 'P->ReactElement): ComponentClass<'P> = jsNative

type [<Pojo>] MyProps =
    { index: int; value: string }

let SortableItem: ComponentClass<MyProps> =
    importHigherOrderComponent "SortableElement" "react-sortable-hoc" (fun p ->
        li [] [str p.value])

// To be used like...
let render () =
    from SortableItem { index = 1; value = "foo" } []

But I'm not sure about a few things:

  • Name? importHigherOrderComponent or something else?
  • Only for imported components or also accepting ComponentClass?
  • The generic 'Props of the returned HOC should be always the same as the argument of the function argument?
  • Probably we also need to add a helper alias instead of from like ofType, ofFunction... Maybe ofComponent here?

@zaaack @MangelMaxime what do you think?

@danyx23 About the documentation, if you could send a PR that'd be great. Don't worry too much about the details, we can work on that later together. The important thing to keep in mind is Fable compiles directly to JS, not JSX. So usually calling React.createElement, passing the component as the first element, a plain JS object containing the props, and filling the rest of arguments with children (as the fable-react helpers do) usually works.

@MangelMaxime
Copy link
Member

I never used HOC so I don't know and don't really understand the hight order think.

@alfonsogarciacaro
Copy link
Member

@MangelMaxime They're used for example for react-sortable-hoc.

@forki
Copy link
Collaborator

forki commented Apr 17, 2018 via email

@danyx23
Copy link
Contributor Author

danyx23 commented Apr 17, 2018

@alfonsogarciacaro cool I'll draft a PR in the next few days. I can't really comment on the finer points yet but I'll go over the docs again and look at some usage, maybe I will have a bit more input then.

@forki it's a silly name but it what is used in the official react docs: https://reactjs.org/docs/higher-order-components.html. As such I would stay with higher order components as that is what people coming from react would be looking for and how third party components that follow this pattern would describe themselves.

@zaaack
Copy link
Contributor

zaaack commented Apr 17, 2018

I know react-sortable-hoc, but never use it before. I'm OK with the naming, but not sure about the details, it's a rare case for me, but it looks really useful.

@alfonsogarciacaro
Copy link
Member

Thanks a lot for your comments! Yes, so far we've tried to closely follow the React terminology to prevent mismatches with React documentation. We should keep the reference to HOC in this case here too.

@alfonsogarciacaro
Copy link
Member

I forgot to mention @vbfox to know his opinion about the helper for React HOCs (please see comment above).

@danyx23
Copy link
Contributor Author

danyx23 commented Apr 21, 2018

I added a first draft of the docs - please let me know what you think!

@runefs
Copy link

runefs commented Jul 25, 2018

Did you make any progress on the importHigherOrderComponent? Was look for a way to integrate react-sortable-hoc into my project to day and found this thread but couldn't figure out whether you have implemented it y a different name

@MangelMaxime
Copy link
Member

Look at this comments: #84 (comment)

I think it should give you a solution

@alfonsogarciacaro
Copy link
Member

@runefs We haven't added the helper yet to Fable.React but here's how I'm using react-sortable-hoc in my project, hope it helps:

Using react-sortable-hoc from Fable
open Fable.Helpers.React
open Fable.Core.JsInterop

/// Ugly helper that should be moved to fable-react
let inline hocOfImport<'P> (importMember: string) (importPath: string) (fn: 'P->ReactElement): ComponentClass<'P> =
    !!((import importMember importPath) $ fn)

// let arrayMove(xs: 'a[], oldIndex: int, newIndex: int): 'a[] = importMember "react-sortable-hoc"

type SortableElementProps<'a> =
    {
      // SortableElement HOC props (cannot be used by wrapped component)
      // https://github.com/clauderic/react-sortable-hoc#sortableelement-hoc
      index: int
      disabled: bool
      // Custom props
      rowIndex: int
      key: string
      value: 'a
      handle: ComponentClass<unit>
      renderItem: ComponentClass<unit> -> int -> 'a -> ReactElement
    }

type SortEnd =
    { oldIndex: int
      newIndex: int }

type SortableContainerProps<'a> =
    {
      // SortableContainer HOC props (cannot be used by wrapped component)
      // https://github.com/clauderic/react-sortable-hoc#sortablecontainer-hoc
      onSortEnd: SortEnd->unit
      lockAxis: string
      // Custom props
      items: 'a list
      handle: ComponentClass<unit>
      useDragHandle: bool
      renderItem: ComponentClass<unit> -> int -> 'a -> ReactElement
      renderList: ReactElement list -> ReactElement
      disabled: bool
    }


let mkSortableHandle (f: unit -> ReactElement) =
    hocOfImport "SortableHandle" "react-sortable-hoc" f


let mkSortableElement<'a> () =
    hocOfImport "SortableElement" "react-sortable-hoc"
        (fun (p: SortableElementProps<'a>) -> p.renderItem p.handle p.rowIndex p.value)


let mkSortableContainer<'a> (sortableElement: ComponentClass<SortableElementProps<'a>>) =
    hocOfImport "SortableContainer" "react-sortable-hoc"
        (fun (p: SortableContainerProps<'a>) ->
            p.items |> List.mapi (fun i v ->
                let a =
                    {
                      index = i
                      disabled = p.disabled
                      rowIndex = i
                      key = "item-" + string i // TODO: unique keys not depending on order
                      value = v
                      handle = p.handle
                      renderItem = p.renderItem
                    }
                from sortableElement a [])
            |> p.renderList)


type SortableComponentProps<'a> =
    {
        renderHandle: unit->ReactElement
        renderItem: ComponentClass<unit> -> int -> 'a -> ReactElement
        renderList: ReactElement list -> ReactElement
        items: 'a list
        /// oldIndex: int -> newIndex: int -> unit
        handleSortChanged : int -> int -> unit
        disabled: bool
    }


type SortableComponent<'a>(p) =
    inherit React.PureComponent<SortableComponentProps<'a>, unit>(p)
    // Cache the SortableContainer HOC in the constructor so it's only necessary to build it once
    let sortableContainer = mkSortableElement () |> mkSortableContainer
    let sortableHandle = mkSortableHandle p.renderHandle


    override this.render() =
        from sortableContainer
            {
                // arrayMove(List.toArray this.props.items, i.oldIndex, i.newIndex)
                onSortEnd  = fun i -> this.props.handleSortChanged i.oldIndex i.newIndex
                lockAxis   = "y"
                items      = this.props.items
                handle     = sortableHandle
                useDragHandle = true
                renderItem = this.props.renderItem
                renderList = this.props.renderList
                disabled   = this.props.disabled
            } []

/// handleSortChanged: oldIndex: int -> newIndex: int -> unit
let mkSortable
    (enabled: bool)
    (renderList: ReactElement list -> ReactElement)
    (renderItem: ComponentClass<unit> -> int -> 'a -> ReactElement)
    (renderHandle: unit -> ReactElement)
    (items: 'a list)
    (handleSortChanged: int -> int -> unit) =

    ofType<SortableComponent<'a>,_,_>
        {
            renderHandle = renderHandle
            renderItem = renderItem
            renderList = renderList
            items = items
            handleSortChanged = handleSortChanged
            disabled = not enabled
        } []

@olivercoad
Copy link

Using a discriminated union props type as documented in using-third-party-react-components.md, how do you add support for IHTMLProp?
Fulma has a Props of IHTMLProp list case in the options DU types but I'm not sure how to do that for 3rd party react components.

@alfonsogarciacaro
Copy link
Member

Unfortunately this involves a bit of a trick at the moment. When passing the props to react we need to convert them to a JS object, which is done with keyValueList. Right now, props nested in another union case are not supported by default, so you have to make the conversion by cheating the compiler as it's done for example here with the Style props:

#if !FABLE_COMPILER
| Style of CSSProp list
| Data of string * obj
#endif
interface IHTMLProp
#if FABLE_COMPILER
let inline Style (css: CSSProp list): HTMLAttr =
!!("style", keyValueList CaseRules.LowerFirst css)

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

7 participants