From 404fe2992a8b30ecf1c88a75b9c8f9949eaf1f31 Mon Sep 17 00:00:00 2001 From: Daniel Bachler Date: Sat, 21 Apr 2018 20:19:38 +0200 Subject: [PATCH 1/8] First stab at documentation on how to use third party react components --- README.md | 1 + docs/using-third-party-react-components.md | 90 ++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 docs/using-third-party-react-components.md diff --git a/README.md b/README.md index 01cd5839..e3534439 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/docs/using-third-party-react-components.md b/docs/using-third-party-react-components.md new file mode 100644 index 00000000..d3eadc61 --- /dev/null +++ b/docs/using-third-party-react-components.md @@ -0,0 +1,90 @@ +# Using third party React components + +Using a third party (Javascript) React component is straightforward for most components. There are two ways of declaring a third party React component in F# - either by declaring a Pojo record for the props or by declaring a Discriminated Union where each case has one field. + +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 safer. + +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 install 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 `[]` attribute can be very useful for defining helper types: + +```fsharp +[] +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. + +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. + +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" }`) + +```fsharp +let inline progressLine (props : ProgressProps list) (elems : ReactElement list) : ReactElement = + ofImport "Line" "rc-progress" (keyValueList CaseRules.LowerFirst props) elems +``` + +### 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" ] [] ] +``` + +## Dynamic import using a Pojo + +The dynamic import is similar to the approach above, but instead of declaring a DU you create a Pojo record. This looks more like normal F# code but can be unwieldy if you have a lot of optional props (which is often the case in complex React components) + +```fsharp +[] +type ProgressProps = + { percent : int + strokeWidth : int + strokeColor : string + } + +let inline progressLine (props : ProgressProps) (elems : ReactElement list) : ReactElement = + ofImport "Line" "rc-progress" props elems +``` + +## 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! From c11ebc8288cbf30b93a2f7f31c03f4b5180d9b8c Mon Sep 17 00:00:00 2001 From: Daniel Bachler Date: Sun, 22 Apr 2018 10:29:56 +0200 Subject: [PATCH 2/8] Fix time safer typo, add header --- docs/using-third-party-react-components.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/using-third-party-react-components.md b/docs/using-third-party-react-components.md index d3eadc61..62164940 100644 --- a/docs/using-third-party-react-components.md +++ b/docs/using-third-party-react-components.md @@ -2,7 +2,9 @@ Using a third party (Javascript) React component is straightforward for most components. There are two ways of declaring a third party React component in F# - either by declaring a Pojo record for the props or by declaring a Discriminated Union where each case has one field. -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 safer. +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. + +## Using a React component by declaring a Discriminated Union props type The basic steps when working with a Discriminated Union are: From 57ea2892f9ca46a9055309f13dee2a4b60a8e5f1 Mon Sep 17 00:00:00 2001 From: Daniel Bachler Date: Sun, 22 Apr 2018 10:30:14 +0200 Subject: [PATCH 3/8] Add link to string enum docs --- docs/using-third-party-react-components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/using-third-party-react-components.md b/docs/using-third-party-react-components.md index 62164940..8f413b0d 100644 --- a/docs/using-third-party-react-components.md +++ b/docs/using-third-party-react-components.md @@ -31,7 +31,7 @@ type ProgressProps = | 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 `[]` attribute can be very useful for defining helper types: +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 `[]` 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 [] From 596fbe8f831245442f3694f194d1b5c142f77f10 Mon Sep 17 00:00:00 2001 From: Daniel Bachler Date: Sun, 22 Apr 2018 10:30:32 +0200 Subject: [PATCH 4/8] Fix content link --- docs/using-third-party-react-components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/using-third-party-react-components.md b/docs/using-third-party-react-components.md index 8f413b0d..0b794c5a 100644 --- a/docs/using-third-party-react-components.md +++ b/docs/using-third-party-react-components.md @@ -89,4 +89,4 @@ let inline progressLine (props : ProgressProps) (elems : ReactElement list) : Re ## 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! +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! From c55d0be0d827eca95ef5abc7e9ef97cf8fd19f6f Mon Sep 17 00:00:00 2001 From: Daniel Bachler Date: Sun, 22 Apr 2018 10:41:42 +0200 Subject: [PATCH 5/8] Split outline into three approaches instead of two, add a TOC to make it easier to understand the structure of the document --- docs/using-third-party-react-components.md | 38 +++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/docs/using-third-party-react-components.md b/docs/using-third-party-react-components.md index 0b794c5a..98bbc867 100644 --- a/docs/using-third-party-react-components.md +++ b/docs/using-third-party-react-components.md @@ -1,9 +1,26 @@ -# Using third party React components +## Using third party React components -Using a third party (Javascript) React component is straightforward for most components. There are two ways of declaring a third party React component in F# - either by declaring a Pojo record for the props or by declaring a Discriminated Union where each case has one field. +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 + + + +- [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) + + + ## Using a React component by declaring a Discriminated Union props type The basic steps when working with a Discriminated Union are: @@ -71,9 +88,11 @@ let view (model : Model) (dispatch : Msg -> unit) = [ progressLine [ percent model.currentProgress; strokeColor: "red" ] [] ] ``` -## Dynamic import using a Pojo +## 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 dynamic import is similar to the approach above, but instead of declaring a DU you create a Pojo record. This looks more like normal F# code but can be unwieldy if you have a lot of optional props (which is often the case in complex React components) +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 [] @@ -87,6 +106,17 @@ let inline progressLine (props : ProgressProps) (elems : ReactElement list) : Re 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! From 48953ce28615eb97169344d68a581bcec7df7f29 Mon Sep 17 00:00:00 2001 From: Daniel Bachler Date: Sun, 22 Apr 2018 10:53:13 +0200 Subject: [PATCH 6/8] Add note on default/member/namespace imports --- docs/using-third-party-react-components.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/using-third-party-react-components.md b/docs/using-third-party-react-components.md index 98bbc867..fcfc68a7 100644 --- a/docs/using-third-party-react-components.md +++ b/docs/using-third-party-react-components.md @@ -66,15 +66,19 @@ type SomeComponentProps = Using the `ofImport` function you instruct Fable which component should be instantiated when the creation function is called. -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. +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. -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 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. From 5970b5522fcb2e46b93d5469775150eeb04c052b Mon Sep 17 00:00:00 2001 From: Daniel Bachler Date: Sun, 22 Apr 2018 13:09:19 +0200 Subject: [PATCH 7/8] Fix syntax in of prop usage --- docs/using-third-party-react-components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/using-third-party-react-components.md b/docs/using-third-party-react-components.md index fcfc68a7..380beb60 100644 --- a/docs/using-third-party-react-components.md +++ b/docs/using-third-party-react-components.md @@ -89,7 +89,7 @@ To use the component in a [Fable-Elmish](https://fable-elmish.github.io/elmish/) let view (model : Model) (dispatch : Msg -> unit) = div [] - [ progressLine [ percent model.currentProgress; strokeColor: "red" ] [] ] + [ progressLine [ percent model.currentProgress; strokeColor "red" ] [] ] ``` ## Importing using a Pojo (plain old JS object) record From 9249d731bd53cd79afc12279ac6ec2da61812885 Mon Sep 17 00:00:00 2001 From: Daniel Bachler Date: Sun, 22 Apr 2018 14:53:30 +0200 Subject: [PATCH 8/8] Fix yarn install -> yarn add --- docs/using-third-party-react-components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/using-third-party-react-components.md b/docs/using-third-party-react-components.md index 380beb60..38243015 100644 --- a/docs/using-third-party-react-components.md +++ b/docs/using-third-party-react-components.md @@ -32,7 +32,7 @@ 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 install rc-progress +yarn add rc-progress ``` ### 2. Define the props type