|
| 1 | +# Using third party React components |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +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. |
| 6 | + |
| 7 | +The basic steps when working with a Discriminated Union are: |
| 8 | + |
| 9 | +### 1. Install the react component |
| 10 | + |
| 11 | +Using yarn or npm, install the react component you want to use. |
| 12 | + |
| 13 | +For example to use the [rc-progress React components](https://github.com/react-component/progress), run the following in your Fable project root: |
| 14 | + |
| 15 | +```bash |
| 16 | +yarn install rc-progress |
| 17 | +``` |
| 18 | + |
| 19 | +### 2. Define the props type |
| 20 | + |
| 21 | +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. |
| 22 | + |
| 23 | +For example to expose the percent, strokeWidth and strokeColor props of the rc-progress components: |
| 24 | + |
| 25 | +```fsharp |
| 26 | +type ProgressProps = |
| 27 | + | Percent of int |
| 28 | + | StrokeWidth of int |
| 29 | + | StrokeColor of string |
| 30 | +``` |
| 31 | + |
| 32 | +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: |
| 33 | + |
| 34 | +```fsharp |
| 35 | +[<StringEnum>] |
| 36 | +type Size = |
| 37 | + | Small |
| 38 | + | Normal |
| 39 | + | Big |
| 40 | +
|
| 41 | +type SomeComponentProps = |
| 42 | + | Size of Size |
| 43 | + | ... |
| 44 | +``` |
| 45 | + |
| 46 | +### 3. Define the React component creation function |
| 47 | + |
| 48 | +Using the `ofImport` function you instruct Fable which component should be instantiated when the creation function is called. |
| 49 | + |
| 50 | +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. |
| 51 | + |
| 52 | +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" }`) |
| 53 | + |
| 54 | +```fsharp |
| 55 | +let inline progressLine (props : ProgressProps list) (elems : ReactElement list) : ReactElement = |
| 56 | + ofImport "Line" "rc-progress" (keyValueList CaseRules.LowerFirst props) elems |
| 57 | +``` |
| 58 | + |
| 59 | +### 4. Use the creation function in your view code |
| 60 | + |
| 61 | +The function you declared in step 2 now behaves just like any other React component function. |
| 62 | + |
| 63 | +To use the component in a [Fable-Elmish](https://fable-elmish.github.io/elmish/) view function: |
| 64 | + |
| 65 | +```fsharp |
| 66 | +let view (model : Model) (dispatch : Msg -> unit) = |
| 67 | + div |
| 68 | + [] |
| 69 | + [ progressLine [ percent model.currentProgress; strokeColor: "red" ] [] ] |
| 70 | +``` |
| 71 | + |
| 72 | +## Dynamic import using a Pojo |
| 73 | + |
| 74 | +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) |
| 75 | + |
| 76 | +```fsharp |
| 77 | +[<Pojo>] |
| 78 | +type ProgressProps = |
| 79 | + { percent : int |
| 80 | + strokeWidth : int |
| 81 | + strokeColor : string |
| 82 | + } |
| 83 | +
|
| 84 | +let inline progressLine (props : ProgressProps) (elems : ReactElement list) : ReactElement = |
| 85 | + ofImport "Line" "rc-progress" props elems |
| 86 | +``` |
| 87 | + |
| 88 | +## Edgecases |
| 89 | + |
| 90 | +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! |
0 commit comments