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 use of third party react components (#84) #85

Merged
merged 8 commits into from
Apr 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Fable bindings and helpers for React projects
## Documents

* [Server-Side Rendering tutorial](docs/server-side-rendering.md): A **Pure F#** solution for SSR, **No NodeJS Required!**
* [Using third party React components](docs/using-third-party-react-components.md): How to create binding so that third party Javascript React components can be used like stock React components in Fable code.



Expand Down
126 changes: 126 additions & 0 deletions docs/using-third-party-react-components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
## Using third party React components

Using a third party (Javascript) React component is straightforward for most components. There are three ways of declaring a third party React component in F# - either by declaring a Discriminated Union where each case has one field; by declaring a record type for the props with the Pojo attribute; or by using an untyped list of `(string * obj)` tuples. All three ways are described below.

Some components have a [Typescript](https://www.typescriptlang.org/) definition available, either because the component was authored in Typescript or someone created a type definition for the [Definitely Typed project](https://definitelytyped.org/). If this is the case then you can try the [ts2fable tool](https://github.com/fable-compiler/ts2fable) to convert this React component type definition from Typescript to a Fable type declaration - it might need some tweaking but for components with a big API surface this can be a real time saver.

## Table of contents

<!-- TOC -->

- [Using third party React components](#using-third-party-react-components)
- [Table of contents](#table-of-contents)
- [Using a React component by declaring a Discriminated Union props type](#using-a-react-component-by-declaring-a-discriminated-union-props-type)
- [1. Install the react component](#1-install-the-react-component)
- [2. Define the props type](#2-define-the-props-type)
- [3. Define the React component creation function](#3-define-the-react-component-creation-function)
- [4. Use the creation function in your view code](#4-use-the-creation-function-in-your-view-code)
- [Importing using a Pojo (plain old JS object) record](#importing-using-a-pojo-plain-old-js-object-record)
- [Passing in props as tuples (without a type declaration of the props)](#passing-in-props-as-tuples-without-a-type-declaration-of-the-props)
- [Edgecases](#edgecases)

<!-- /TOC -->

## Using a React component by declaring a Discriminated Union props type

The basic steps when working with a Discriminated Union are:

### 1. Install the react component

Using yarn or npm, install the react component you want to use.

For example to use the [rc-progress React components](https://github.com/react-component/progress), run the following in your Fable project root:

```bash
yarn add rc-progress
```

### 2. Define the props type

Reference the **documentation of the React component** to find out which props the component supports and declare them as an F# type (see below for the two supported mechanisms). You can define only a subset of supported props in F# if you don't need to cover the full props options that the React component supports.

For example to expose the percent, strokeWidth and strokeColor props of the rc-progress components:

```fsharp
type ProgressProps =
| Percent of int
| StrokeWidth of int
| StrokeColor of string
```

If one of the props is treated as a string enum in Javascript (e.g. if there is a size prop with the supported values "small", "normal" and "big"), then the `[<StringEnum>]` attribute can be very useful for defining helper types (see the [StringEnum docs](http://fable.io/docs/interacting.html#stringenum-attribute) for more info):

```fsharp
[<StringEnum>]
type Size =
| Small
| Normal
| Big

type SomeComponentProps =
| Size of Size
| ...
```

### 3. Define the React component creation function

Using the `ofImport` function you instruct Fable which component should be instantiated when the creation function is called.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe quickly warning about importing default members, which is a common source of confusion. You can also link to Fable docs: http://fable.io/docs/interacting.html#importing-javascript-code


There are several different ways to declare exports in Javascript (default imports, member imports, namespace imports); depending on how the Javascript React component was declared, you have to choose the right import. Refer to the [Fable docs](http://fable.io/docs/interacting.html#importing-javascript-code) for more information on imports.

In the example of rc-progress, to declare a `progressLine` creation function that imports the `Line` component from the library `rc-progress`, you would declare it as follows.

```fsharp
let inline progressLine (props : ProgressProps list) (elems : ReactElement list) : ReactElement =
ofImport "Line" "rc-progress" (keyValueList CaseRules.LowerFirst props) elems
```

Note the `keyValueList` function that is used to convert the props of type `IProgressProps list` to a Javascript object where they key is the lower case name of the DU case identifier and the value is the field value of the DU (e.g. if the list that is passed into the function is `[Percent 40; StrokeColor "red"]`, the Javascript object that will be passed to the `props` of the `Line` react component would look like this: `{ percent: 40, strokeColor: "red" }`)

In the docs of the [rc-progress React components](https://github.com/react-component/progress) the import styled used is a *member import*, so we use refer to the component member directly in the ofImport expression.

### 4. Use the creation function in your view code

The function you declared in step 2 now behaves just like any other React component function.

To use the component in a [Fable-Elmish](https://fable-elmish.github.io/elmish/) view function:

```fsharp
let view (model : Model) (dispatch : Msg -> unit) =
div
[]
[ progressLine [ percent model.currentProgress; strokeColor "red" ] [] ]
```

## Importing using a Pojo (plain old JS object) record

The Pojo import is similar to the approach above, but instead of declaring a DU you create a [Pojo record](http://fable.io/docs/interacting.html#plain-old-javascript-objects). Using a record with the Pojo attribute to express the props looks more like idiomatic F# code but it can be unwieldy if you have a lot of optional props. Since this is common with React components, using the DU approach above can often be more convenient.

The Pojo attribute is required on such record types because record definitions without the Pojo attribute get compiled to Javascript classes which cannot be used for props in React; using the Pojo attribute instead instructs the Fable compiler to generate a plain old Js object.

```fsharp
[<Pojo>]
type ProgressProps =
{ percent : int
strokeWidth : int
strokeColor : string
}

let inline progressLine (props : ProgressProps) (elems : ReactElement list) : ReactElement =
ofImport "Line" "rc-progress" props elems
```

## Passing in props as tuples (without a type declaration of the props)

The third way of using a React component is to not give an F# type to the Props at all and simply pass a list of `(string * obj)` tuples to the `createObj` helper function which turns the list into a Javascript object and passes it as the props of the React component. This of course has the least level of type safety but it can be convenient for prototyping. The `==>` operator is defined in the [Fable.Core.JsInterop](http://fable.io/docs/interacting.html#plain-old-javascript-objects) module to make `(string * obj)` tuple creation easier to read.

```fsharp
open Fable.Core.JsInterop

ofImport "Line" "rc-progress" (createObj ["strokeWidth" ==> 5]) []
```


## Edgecases

This documentation needs to be extended to cover [Higher Order Components](https://reactjs.org/docs/higher-order-components.html) and maybe [Context](https://reactjs.org/docs/context.html), [Fragments](https://reactjs.org/docs/fragments.html) etc. Contributions are welcome!