Fable: Rendering Fable React components in a TypeScript React app

Created on 26 Mar 2018  路  8Comments  路  Source: fable-compiler/Fable

I asked this on Gitter, but didn't get any bites. Hopefully this is the correct place to ask.

I've got a question on interop between an NPM library of React components I'd like to port to Fable and using said library from Fable and TypeScript apps.

Say I've created a stateless functional component in TypeScript like this:

const MagicButton : SFC<{buttonType: string}> = (props) =>
  <button className={props.buttonType}>
    {props.children}
  </button>

I would like to be able to reimplement it in Fable and use it from both F# and TypeScript apps. I'd like to keep the children and props separate in Fable apps as it reads a lot better, but not lose the ability to use it in JSX.

I've tried something like

let Button props children =
  button [ Class props.buttonType] children

which works well in Fable, but does not render children in TypeScript. This makes sense because in React children are passed on props and not as a second parameter. I've hacked together something that works:

let UnboxButton props children =
  let pChildren = 
    match (unbox props?children) with
      | Some c -> c
      | _ -> []
  button [ Class props.buttonType ] (unbox pChildren @ children)

which does what I want (renders children in both scenarios), but the code feels hacky and I would rather not use something like this.

Is there a way I can create React components in a Fable project that keep the clean props children signature but still consume them from TypeScript or JavaScript?

Most helpful comment

Interesting, so far I haven't used a React Function component accepting children. It's a bit more verbose but using the class syntax probably works better here:

type [<Pojo>] MyButtonProps = { buttonType: string }

type MyButton(props) =
    inherit React.Component<MyButtonProps, obj>(props)
    override this.render() =
        button [Class this.props.buttonType] (List.ofArray this.children)

You should be able to use this normally with JSX.

import { MyButton } from "./File.fs"

<MyButton buttonType="foo">...</MyButton>

All 8 comments

What about something like this:

// For use from F#
module Components =
  let Button props children =
    button [ Class props.buttonType ] children

// For use from JSX/TSX
module ComponentsJS =
  open Components

  let Button props =
    Button props !!props?children

That would work, but the broken components would still be available from JSX. Unless there's a way control the exports to prevent the F# components from being available, I'd like to have another way.

What I want might not be possible right now. It's just something I'd like to be able to do. If it's at all possible but not implemented right now, I'd be happy to make the modifications necessary (with some guidance, of course).

If you mark a function as private then it will not be exposed in the js export variable. Perhaps that's the solution ?

Making those functions private would prevent me from using the props children style component in other Fable projects outside of the library, right? I'd like to use this library from Fable and TypeScript projects simultaneously, just with different signature styles.

I'm playing around with some alternatives, one of which is an operator I'd end up using whenever I render children in an exported component. Not my ideal solution, but it at least gets things moving.

Yes, true didn't think about the F# implication of using private.

@timothymclane Perhaps you can define compiler directives by env values in webpack.config.js? (see https://github.com/fable-compiler/fable-templates/blob/master/simple/Content/tools/webpack.config.common.js#L49)

e.g.

define: (process.env.TARGET === 'ts' ? ['TS'] : []).concat(isProduction ? [] : ["DEBUG"])

```F#
// For use from F#
module Components =

if !TS

let Button props children =
button props children

else

let Button props =
button props props?children

endif

```

With this, you also need to set different webpack's output path in different targets.

Interesting, so far I haven't used a React Function component accepting children. It's a bit more verbose but using the class syntax probably works better here:

type [<Pojo>] MyButtonProps = { buttonType: string }

type MyButton(props) =
    inherit React.Component<MyButtonProps, obj>(props)
    override this.render() =
        button [Class this.props.buttonType] (List.ofArray this.children)

You should be able to use this normally with JSX.

import { MyButton } from "./File.fs"

<MyButton buttonType="foo">...</MyButton>

The class syntax isn't so bad and does what I need. I have a strong preference toward functions instead of classes for React components, especially for presentational components that don't need lifecycle methods or access to internal state.

If there's a feasible way to get it working with stateless functional components the way it works with class-based components, that'd be my ideal scenario. But this gets me moving for now. Thanks for the help!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

forki picture forki  路  3Comments

jwosty picture jwosty  路  3Comments

funlambda picture funlambda  路  4Comments

alfonsogarciacaro picture alfonsogarciacaro  路  3Comments

alfonsogarciacaro picture alfonsogarciacaro  路  3Comments