" ]
164 | ]
165 | ]
166 |
167 | div [ ClassName "test-case" ] [
168 | span [ ClassName "label" ] [ str "Test js component:" ]
169 | isomorphicView jsComp jsCompServer { text="I'm rendered by a js Component!" }
170 | ]
171 |
172 | div [ ClassName "test-case" ] [
173 | span [ ClassName "label" ] [ str "Test ofType:" ]
174 | ofType { text="my prop" } [ span [] [ str " I'm rendered by children!"] ]
175 | ]
176 |
177 | div [ ClassName "test-case" ] [
178 | span [ ClassName "label" ] [ str "Test null:" ]
179 | null
180 | ]
181 |
182 | div [ ClassName "test-case" ] [
183 | span [ ClassName "label" ] [ str "Test ofFunction:" ]
184 | ofFunction fnComp { text = "I'm rendered by Function Component!"} []
185 | ofFunction fnCompWithChildren { text = " I'm rendered by Function Component! "; children=[||]} [ span [] [ str " I'm rendered by children!"] ]
186 | ]
187 |
188 | div [ ClassName "test-case" ] [
189 | span [ ClassName "label" ] [ str "Test void elements:" ]
190 | hr [ Style [ BorderColor "green" ] ]
191 | br []
192 | ]
193 |
194 | div [ ClassName "test-case" ] [
195 | span [ ClassName "label" ] [ str "Test add slug to attributes:" ]
196 | div
197 | [ HTMLAttr.Custom ("contentEditable", "true")
198 | HTMLAttr.Custom ("placeholder", "I'm editable!")
199 | Style [ CSSProp.Custom ("-webkit-transform", "translateX(30px)"); CSSProp.Custom ("-webkit-transform-origin", "0 0") ] ]
200 | [ ]
201 | ]
202 | ]
203 |
--------------------------------------------------------------------------------
/SSRSample/src/Shared/jsComp.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function JsComp({ text }) {
4 | return React.createElement('div', { className: 'js-comp' }, text)
5 | }
6 |
--------------------------------------------------------------------------------
/SSRSample/src/Shared/paket.references:
--------------------------------------------------------------------------------
1 | group Server
2 | FSharp.Core
3 |
4 | group Client
5 | Fable.Core
6 | Fable.Browser.Dom
7 |
--------------------------------------------------------------------------------
/SSRSample/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | const execSync = require("child_process").execSync;
4 |
5 | var CONFIG = {
6 | fsharpEntry: './src/Client/Client.fsproj',
7 | outputDir: './src/Client/public',
8 | assetsDir: './src/Client',
9 | devServerPort: 8080,
10 | devServerProxy: {
11 | '/api/*': {
12 | target: 'http://localhost:8085',
13 | }
14 | },
15 | // Use babel-preset-env to generate JS compatible with most-used browsers.
16 | // More info at https://babeljs.io/docs/en/next/babel-preset-env.html
17 | babel: {
18 | presets: [
19 | ['@babel/preset-env', {
20 | modules: false,
21 | useBuiltIns: 'usage',
22 | corejs: 3
23 | }]
24 | ],
25 | }
26 | }
27 |
28 | // If we're running the webpack-dev-server, assume we're in development mode
29 | var isProduction = !process.argv.find(v => v.indexOf('webpack-dev-server') !== -1);
30 | console.log('Bundling for ' + (isProduction ? 'production' : 'development') + '...');
31 |
32 |
33 | var isGitPod = process.env.GITPOD_INSTANCE_ID !== undefined;
34 |
35 | function getDevServerUrl() {
36 | if (isGitPod) {
37 | const url = execSync(`gp url ${CONFIG.devServerPort}`);
38 | return url.toString().trim();
39 | } else {
40 | return `http://localhost:${CONFIG.devServerPort}`;
41 | }
42 | }
43 |
44 | module.exports = {
45 | entry: resolve(CONFIG.fsharpEntry),
46 | output: {
47 | path: resolve(CONFIG.outputDir),
48 | filename: 'bundle.js'
49 | },
50 | mode: isProduction ? 'production' : 'development',
51 | devtool: isProduction ? 'source-map' : 'eval-source-map',
52 | plugins: isProduction ? [] : [new webpack.HotModuleReplacementPlugin()],
53 | devServer: {
54 | public: getDevServerUrl(),
55 | publicPath: '/public',
56 | contentBase: resolve(CONFIG.assetsDir),
57 | host: '0.0.0.0',
58 | allowedHosts: ['localhost', '.gitpod.io'],
59 | port: CONFIG.devServerPort,
60 | proxy: CONFIG.devServerProxy,
61 | hot: true,
62 | inline: true
63 | },
64 | module: {
65 | rules: [
66 | {
67 | test: /\.fs(x|proj)?$/,
68 | use: {
69 | loader: 'fable-loader',
70 | options: {
71 | babel: CONFIG.babel
72 | }
73 | }
74 | },
75 | ]
76 | }
77 | };
78 |
79 | function resolve(filePath) {
80 | return path.isAbsolute(filePath) ? filePath : path.join(__dirname, filePath);
81 | }
82 |
--------------------------------------------------------------------------------
/Settings.FSharpLint:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | False
6 |
7 |
8 |
--------------------------------------------------------------------------------
/build.fsx:
--------------------------------------------------------------------------------
1 | #r "nuget: Fable.PublishUtils, 2.4.0"
2 |
3 | open PublishUtils
4 |
5 | let args =
6 | fsi.CommandLineArgs
7 | |> Array.skip 1
8 | |> List.ofArray
9 |
10 | match args with
11 | | IgnoreCase "publish"::_ ->
12 | pushFableNuget "src/Fable.React.Types/Fable.React.Types.fsproj" [] doNothing
13 | pushFableNuget "src/Fable.ReactDom.Types/Fable.ReactDom.Types.fsproj" [] doNothing
14 | pushFableNuget "src/Fable.React/Fable.React.fsproj" [] doNothing
15 | | _ -> ()
16 |
--------------------------------------------------------------------------------
/docs/react-error-boundaries.md:
--------------------------------------------------------------------------------
1 | # How to use react error boundaries in fable
2 |
3 | When react renders the view and encounters an error it will by default just crash the complete application.
4 | If your html page only contains a single react element your page will be essentially a white page from this point and the user needs to reload the website.
5 |
6 | The problem is that a simple `try-catch` in the view will not be enough to handle errors in the render-pipeline.
7 | In order to catch these errors you need to overwrite `componentDidCatch`, this process is documented [here](https://reactjs.org/docs/error-boundaries.html)
8 |
9 | While render-errors are rare when using fable it can happen, especially when embedding 3rd-party-components.
10 | Because of this we provide a general purpose ReactErrorBoundary component which can be added and used from your application (including elmish-architecture)
11 |
12 |
13 | ## ReactErrorBoundaries.fs
14 |
15 | Just copy the following code to your project:
16 |
17 | ```fsharp
18 | module ReactErrorBoundary
19 |
20 | open Fable.Core
21 | open Fable.React
22 |
23 | type [] InfoComponentObject =
24 | abstract componentStack: string with get
25 |
26 | type ErrorBoundaryProps =
27 | { Inner : React.ReactElement
28 | ErrorComponent : React.ReactElement
29 | OnError : exn * InfoComponentObject -> unit }
30 |
31 | type ErrorBoundaryState =
32 | { HasErrors : bool }
33 |
34 | // See https://github.com/MangelMaxime/Fulma/blob/master/docs/src/Widgets/Showcase.fs
35 | // See https://reactjs.org/docs/error-boundaries.html
36 | type ErrorBoundary(props) =
37 | inherit React.Component(props)
38 | do base.setInitState({ HasErrors = false })
39 |
40 | override x.componentDidCatch(error, info) =
41 | let info = info :?> InfoComponentObject
42 | x.props.OnError(error, info)
43 | x.setState({ HasErrors = true })
44 |
45 | override x.render() =
46 | if (x.state.HasErrors) then
47 | x.props.ErrorComponent
48 | else
49 | x.props.Inner
50 |
51 | let renderCatchSimple errorElement element =
52 | ofType { Inner = element; ErrorComponent = errorElement; OnError = fun _ -> () } [ ]
53 |
54 | let renderCatchFn onError errorElement element =
55 | ofType { Inner = element; ErrorComponent = errorElement; OnError = onError } [ ]
56 | ```
57 |
58 | ## Usage
59 |
60 | Usage from an elmish application could look similar to this:
61 |
62 | Consider you have a `SubComponent` which might run into rendering issues and an `ErrorComponent` you want to show if a rendering issue occurs:
63 |
64 | ```fsharp
65 | let view model dispatch =
66 | SubComponent.view model dispatch
67 | |> ReactErrorBoundary.renderCatchFn
68 | (fun (error, info) ->
69 | //dispatch (MyMessage ...)
70 | logger.Error("SubComponent failed to render" + info.componentStack, error))
71 | (ErrorComponent.view model dispatch)
72 | ```
73 |
74 | If you don't need the `OnError` event:
75 |
76 | ```fsharp
77 | let view model dispatch =
78 | SubComponent.view model dispatch
79 | |> ReactErrorBoundary.renderCatchSimple
80 | (ErrorComponent.view model dispatch)
81 | ```
82 |
--------------------------------------------------------------------------------
/docs/server-side-rendering.md:
--------------------------------------------------------------------------------
1 | # Five steps to enable Server-Side Rendering in your [Elmish](https://github.com/fable-elmish/elmish) + [DotNet Core](https://github.com/dotnet/core) App!
2 |
3 | > [SSR Sample App](https://github.com/fable-compiler/fable-react/tree/master/SSRSample) based on [SAFE-Stack](https://github.com/SAFE-Stack/SAFE-BookStore) template is available!
4 |
5 | ## Introduction
6 |
7 | ### What is Server-Side Rendering (SSR) ?
8 |
9 | Commonly speaking SSR means the majority of your app's code can run on both the server and the client, it is also as known as "isomorphic app" or "universal app". In React, you can render your components to html on the server side (usually a nodejs server) by `ReactDOMServer.renderToString`, reuse the server-rendered html and bind events on the client side by `React.hydrate`.
10 |
11 | #### Pros
12 |
13 | * Better SEO, as the search engine crawlers will directly see the fully rendered page.
14 | * Faster time-to-content, especially on slow internet or slow devices.
15 |
16 | #### Cons
17 |
18 | * Development constraints, browser-specific code need add compile directives to ignore in the server.
19 | * More involved build setup and deployment requirements.
20 | * More server-side load.
21 |
22 | #### Conclusions
23 |
24 | While SSR looks pretty cool, it still adds more complexity to your app, and increases server-side load. But it could be really helpful in some cases like solving SEO issue in SPAs, improving time-to-content of mobile sites, etc.
25 |
26 | ### Server-Side Rendering in fable-react
27 |
28 | fable-react's SSR approach is a little different from those you see on the network, it is a **Pure F#** approach. It means you can render your elmish's view function directly on dotnet core, with all benefits of dotnet core runtime!
29 |
30 | There are lots of articles about comparing dotnet core and nodejs, I will only mention two main differences between F#/dotnet core and nodejs in SSR:
31 |
32 | * F# is a compiled language, which means it's generally considered faster then a dynamic language, like js.
33 | * Nodejs's single thread, event-driven, non-blocking I/O model works well in most web sites, but it is not good at CPU intensive tasks, including html rendering. Usually we need to run multi nodejs instances to take the advantage of multi-core systems. DotNet support non-blocking I/O (and `async/await` sugar), too. But the awesome part is that it also has pretty good support for multi-thread programming.
34 |
35 | In a simple test, rendering on dotnet core is about ~1.5x faster then nodejs (with ReactDOMServer.renderToString + NODE_ENV=production) in a single thread. You can find more detail in the bottom of this page.
36 |
37 | In a word, with this approach, you can not only get a better performance then nodejs, but also don't need the complexity of running and maintaining nodejs instances on your server!
38 |
39 | Here is a list of Fable.Helpers.React API that support server-side rendering:
40 |
41 | * HTML/CSS/SVG DSL function/unions, like `div`, `input`, `Style`, `Display`, `svg`, etc.
42 | * str/ofString/ofInt/ofFloat
43 | * ofOption/ofArray/ofList
44 | * fragment
45 | * ofType
46 | * ofFunction
47 |
48 | These don't support, but you can wrap it by `Fable.React.Isomorphic.isomorphicView` to skip or render a placeholder on the server:
49 |
50 | * ofImport
51 |
52 | ## Step 1: Reorganize your source files
53 |
54 | Separate all your elmish view and types to standalone files, like this:
55 |
56 | ```F#
57 |
58 | pages
59 | |-- Home
60 | |-- View.fs // contains view function.
61 | |-- Types.fs // contains msg and model type definitions, also should include init function.
62 | |-- State.fs // contains update function
63 |
64 | ```
65 |
66 | View.fs and Types.fs will be shared between client and server.
67 |
68 | ## Step 2. Make sure shared files can be executed on the server side
69 |
70 | Some code that works in Fable might throw a runtime exception on dotnet core, we should be careful with unsafe type casting and add compiler directives to remove some code if necessary.
71 |
72 | Here are some hints about doing this:
73 |
74 | ### 1. Replace unsafe cast (unbox and `!!`) in your HTML attributes and CSS props with `HTMLAttr.Custom`, `SVGAttr.Custom` and `CSSProp.Custom`
75 |
76 | ```diff
77 | - div [ !!("class", "container") ] []
78 | + div [ HTMLAttr.Custom ("class", "container") ]
79 |
80 |
81 | - div [ Style [ !!("class", "container") ] ] []
82 | + div [ Style [ CSSProp.Custom("class", "container") ] ] []
83 |
84 | - svg [ !!("width", 100) ] []
85 | + svg [ SVGAttr.Custom("class", "container") ] []
86 | ```
87 |
88 |
89 | ### 2. Make sure your browser/js code won't be executed on the server side
90 |
91 | One big challenge of sharing code between client and server is that the server side has different API environment with client side. In this respect Fable + dotnet core's SSR is not much different than nodejs, except on dotnet core you should not only prevent browser's API call, but also js.
92 |
93 | Thanks for Fable Compiler's `FABLE_COMPILER` directive, we can easily distinguish it's running on client or server and execute different code in different environment:
94 |
95 | ```#F
96 | #if FABLE_COMPILER
97 | executeOnClient ()
98 | #else
99 | executeOnServer ()
100 | #endif
101 | ```
102 |
103 | We also provide a help function in `Fable.Helpers.Isomorphic`, the definition is:
104 |
105 | ```F#
106 | let inline isomorphicExec clientFn serverFn input =
107 | #if FABLE_COMPILER
108 | clientFn input
109 | #else
110 | serverFn input
111 | #endif
112 | ```
113 |
114 | Full example:
115 |
116 | ```diff
117 | open Fable.Core
118 | open Fable.Core.JS
119 | open Fable.React.Isomorphic
120 | open Browser
121 |
122 | // example code to add marquee effect to your document's title
123 | -window.setInterval(
124 | - fun () ->
125 | - document.title <- document.title.[1..len - 1] + document.title.[0..0],
126 | - 600
127 | -)
128 |
129 |
130 | +let inline clientFn () =
131 | + window.setInterval(
132 | + fun () ->
133 | + document.title <- document.title.[1..len - 1] + document.title.[0..0],
134 | + 600
135 | + )
136 | +isomorphicExec clientFn ignore ()
137 | ```
138 |
139 |
140 | ### 3. Add a placeholder for components that cannot been rendered on the server side, like js native components.
141 |
142 | In `Fable.React.Isomorphic` we also implemented a help function (`isomorphicView`) to render a placeholder element for components that cannot be rendered on the server side, this function will also help [React.hydrate](https://reactjs.org/docs/react-dom.html#hydrate) to understand the differences between htmls rendered by client and server, so React won't treat it as a mistake and warn about it.
143 |
144 | ```diff
145 | open Fable.Core
146 | open Fable.Core.JS
147 | open Fable.React
148 | open Fable.React.Isomorphic
149 | open Browser
150 |
151 | type JsCompProps = {
152 | text: string
153 | }
154 |
155 | let jsComp (props: JsCompProps) =
156 | ofImport "default" "./jsComp" props []
157 |
158 | -jsComp { text="I'm rendered by a js Component!" }
159 |
160 | +let jsCompServer (props: JsCompProps) =
161 | + div [] [ str "loading" ]
162 | +
163 | +isomorphicView jsComp jsCompServer { text="I'm rendered by a js Component!" }
164 | ```
165 |
166 | ## Step 3. Create your initial state on the server side.
167 |
168 | On the server side, you can create routes like normal MVC app, just make sure the model passed to server-side rendering function is exactly match the model on the client side in current route.
169 |
170 | Here is an example:
171 |
172 | ```F#
173 |
174 | open Giraffe
175 | open Giraffe.GiraffeViewEngine
176 | open FableJson
177 |
178 | let initState: Model = {
179 | counter = Some 42
180 | someString = "Some String"
181 | someFloat = 11.11
182 | someInt = 22
183 | }
184 |
185 | let renderHtml () =
186 | // This would render the html by model create on the server side.
187 | // Note in an Elmish app, view function takes two parameters,
188 | // the first is model, and the second is dispatch,
189 | // which simple ignored here because React will bind event handlers for you on the client side.
190 | let htmlStr = Fable.Helpers.ReactServer.renderToString(Client.View.view initState ignore)
191 |
192 | // We also need to pass the model to Elmish and React by print a json string in html to let them know what's the model that used to rendering the html.
193 | // Note we call ofJson twice here,
194 | // because Elmish's model can contains some complicate type instead of pojo,
195 | // the first one will seriallize the state to json string,
196 | // and the second one will seriallize the json string to a legally js string,
197 | // so we can deseriallize it by Fable's ofJson and get the correct types.
198 | let stateJsonStr = toJson (toJson initState)
199 |
200 | html []
201 | [ head [] []
202 | body []
203 | [ div [_id "elmish-app"] [ rawText htmlStr ]
204 | script []
205 | [ rawText (sprintf """
206 | var __INIT_STATE__ = %s
207 | """ stateJsonStr) ] //
208 | script [ _src (assetsBaseUrl + "/public/bundle.js") ] []
209 | ]
210 | ]
211 | ```
212 |
213 | ## Step 4. Update your elmish app's init function
214 |
215 | 1. Initialize your elmish app by state printed in the HTML.
216 | 2. Remove initial commands that fetch state which already included in the HTML.
217 |
218 | e.g.
219 |
220 | ```F#
221 | let init () =
222 | // Init model by server side state
223 | let model = ofJson !!window?__INIT_STATE__
224 | // let cmd =
225 | // Cmd.ofPromise
226 | // (fetchAs "/api/init")
227 | // []
228 | // (Ok >> Init)
229 | // (Error >> Init)
230 | model, Cmd.none
231 | ```
232 |
233 | ## Step 5. Using React.hydrate to render your app
234 |
235 | ```diff
236 | Program.mkProgram init update view
237 | #if DEBUG
238 | |> Program.withConsoleTrace
239 | |> Program.withHMR
240 | #endif
241 | -|> Program.withReact "elmish-app"
242 | +|> Program.withReactHydrate "elmish-app"
243 | #if DEBUG
244 | |> Program.withDebugger
245 | #endif
246 | |> Program.run
247 | ```
248 |
249 | Now enjoy! If you find bugs or just need some help, please create an issue and let us know, thanks!
250 |
251 | ## Try the sample app
252 |
253 | ```sh
254 | git clone https://github.com/fable-compiler/fable-react.git
255 | cd ./fable-react/SSRSample/
256 | ./build.sh run # or ./build.cmd run on windows
257 | ```
258 |
259 | ## Run simple benchmark test in sample app
260 |
261 | The SSRSample project also contains a simple benchmark test, you can try it in you computer by:
262 |
263 | ```sh
264 |
265 | cd ./SSRSample
266 | ./build.sh bench # or ./build.cmd bench on windows
267 |
268 | ```
269 |
270 | Here is the benchmark result on a linux laptop (Intel Core i7-3630QM, 8 core), rendering on dotnet core is about ~1.5x faster then on nodejs in a single thread. To take the advantage of multi-core systems, we also tested with multi-thread on dotnet core and cluster mode in nodejs, the dotnet core version is still faster then nodejs version, but not much. I guess it's because multi-process takes more advantages from multi cores then multi-threaded. What's more, multi-threaded dotnet has less memory footprint.
271 |
272 | ```sh
273 |
274 | dotnet ./bin/Release/netcoreapp2.0/dotnet.dll
275 | Thread 1 started
276 | Thread 1 render 160000 times used 23062ms
277 | [Single thread] 23062ms 6937.820req/s
278 | Thread 1 started
279 | Thread 3 started
280 | Thread 4 started
281 | Thread 6 started
282 | Thread 5 started
283 | Thread 7 started
284 | Thread 10 started
285 | Thread 9 started
286 | Thread 3 render 20000 times used 9593ms
287 | Thread 5 render 20000 times used 9689ms
288 | Thread 10 render 20000 times used 9693ms
289 | Thread 9 render 20000 times used 9705ms
290 | Thread 4 render 20000 times used 9720ms
291 | Thread 1 render 20000 times used 9753ms
292 | Thread 7 render 20000 times used 9757ms
293 | Thread 6 render 20000 times used 9795ms
294 | [8 tasks] Total: 9713ms Memory footprint: 44.063MB Requests/sec: 16472.768
295 |
296 | /usr/local/bin/node ./node.js
297 | Master 10891 is running
298 | [Single process] 34322ms 4661.733req/s
299 | Worker 10911: started
300 | Worker 10916: started
301 | Worker 10928: started
302 | Worker 10942: started
303 | Worker 10930: started
304 | Worker 10935: started
305 | Worker 10922: started
306 | Worker 10951: started
307 | Worker 10911: render 20000 times used 11394ms
308 | Worker 10935: render 20000 times used 11353ms
309 | Worker 10928: render 20000 times used 11522ms
310 | Worker 10922: render 20000 times used 11492ms
311 | Worker 10916: render 20000 times used 11812ms
312 | Worker 10930: render 20000 times used 11913ms
313 | Worker 10951: render 20000 times used 11781ms
314 | Worker 10942: render 20000 times used 12236ms
315 | [8 workers] Total: 11687.875ms Memory footprint: 200.066MB Requests/sec: 13689.400
316 |
317 | ```
318 |
--------------------------------------------------------------------------------
/docs/using-third-party-react-components.md:
--------------------------------------------------------------------------------
1 | ## Using third party React components
2 |
3 | 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.
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 saver.
6 |
7 | ## Table of contents
8 |
9 |
10 |
11 | - [Using third party React components](#using-third-party-react-components)
12 | - [Table of contents](#table-of-contents)
13 | - [Using a React component by declaring a Discriminated Union props type](#using-a-react-component-by-declaring-a-discriminated-union-props-type)
14 | - [1. Install the react component](#1-install-the-react-component)
15 | - [2. Define the props type](#2-define-the-props-type)
16 | - [3. Define the React component creation function](#3-define-the-react-component-creation-function)
17 | - [Member Import](#member-import)
18 | - [Default Import](#default-import)
19 | - [Fields of imported items](#fields-of-imported-items)
20 | - [Directly creating the element](#directly-creating-the-element)
21 | - [4. Use the creation function in your view code](#4-use-the-creation-function-in-your-view-code)
22 | - [5. Get component state back into your code](#5-get-component-state-back-into-your-code)
23 | - [Importing using a Pojo (plain old JS object) record](#importing-using-a-pojo-plain-old-js-object-record)
24 | - [Passing in props as tuples (without a type declaration of the props)](#passing-in-props-as-tuples-without-a-type-declaration-of-the-props)
25 | - [Edgecases](#edgecases)
26 |
27 |
28 |
29 | ## Using a React component by declaring a Discriminated Union props type
30 |
31 | The basic steps when working with a Discriminated Union are:
32 |
33 | ### 1. Install the react component
34 |
35 | Using yarn or npm, install the react component you want to use.
36 |
37 | For example to use the [rc-progress](https://github.com/react-component/progress) React component which we'll be using in this tutorial, run the following command inside your Fable project root folder:
38 |
39 | ```bash
40 | yarn add rc-progress
41 | ```
42 |
43 | ### 2. Define the props type
44 |
45 | 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.
46 |
47 | For example to expose the percent, strokeWidth and strokeColor props of the rc-progress components:
48 |
49 | ```fsharp
50 | type ProgressProps =
51 | | Percent of int
52 | | StrokeWidth of int
53 | | StrokeColor of string
54 | ```
55 |
56 | 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):
57 |
58 | ```fsharp
59 | []
60 | type Size =
61 | | Small
62 | | Normal
63 | | Big
64 |
65 | type SomeComponentProps =
66 | | Size of Size
67 | | ...
68 | ```
69 |
70 | ### 3. Define the React component creation function
71 |
72 | 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.
73 |
74 | Using the `ofImport` function you instruct Fable which component should be instantiated when the creation function is called.
75 |
76 | #### Member Import
77 |
78 | 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.
79 |
80 | ```fsharp
81 | open Fable.Core
82 | open Fable.Core.JsInterop
83 | open Fable.React
84 | open Fable.React.Props
85 |
86 | let inline progressLine (props : ProgressProps list) (elems : ReactElement list) : ReactElement =
87 | ofImport "Line" "rc-progress" (keyValueList CaseRules.LowerFirst props) elems
88 | ```
89 |
90 | The `keyValueList` function is used to convert the props of type `IProgressProps list` to a JavaScript object where the key is the lower case name of the discriminated union case identifier and the value is the field value of the discriminated union (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" }`)
91 |
92 | In the docs of the [rc-progress](https://github.com/react-component/progress) React component the import style used is a *member import* (e.g. `import { Line, Circle } from 'rc-progress';`), so we refer to the component member `Line` directly in the ofImport expression.
93 |
94 | #### Default Import
95 |
96 | If the export is declard as a default export, then you would use ``"default"`` as the member name.
97 | Taking [react-native-qrcode-scanner](https://github.com/moaazsidat/react-native-qrcode-scanner) as an example:
98 |
99 | To translate the example
100 |
101 | ```js
102 | import QRCodeScanner from 'react-native-qrcode-scanner';
103 | ```
104 |
105 | you would declare your function like
106 |
107 | ```fsharp
108 | let inline qr_code_scanner (props : QRCodeScannerProps list) : ReactElement =
109 | ofImport "default" "react-native-qrcode-scanner" (keyValueList CaseRules.LowerFirst props) []
110 | ```
111 |
112 | #### Fields of imported items
113 |
114 | Some React components must be instantiated as follows in JS:
115 |
116 | ```js
117 | import { Select } from 'react-select'
118 | let render = () =>
119 | ```
120 |
121 | In this case, you can also use `ofImport` to directly access the field of the imported item:
122 |
123 | ```fsharp
124 | // Import { Select } from "react-select" and then access the "Creatable" field
125 | ofImport "Select.Creatable" "react-select" myOptions []
126 |
127 | // Also compatible with default imports
128 | ofImport "default.Creatable" "react-select" myOptions []
129 | ```
130 |
131 | #### Directly creating the element
132 |
133 | If you already have a reference to the imported component, then you can also use ``createElement``.
134 |
135 | The default import above could also be rewritten like this:
136 |
137 | ```fsharp
138 | let rnqs = importDefault "react-native-qrcode-scanner"
139 | createElement(rnqs, (keyValueList CaseRules.LowerFirst props), [])
140 | ```
141 |
142 | > Please note it's also OK to duplicate `ofImport` with same member and path. In this case, Fable will automatically group the imports.
143 |
144 | ```fsharp
145 | let foo1 = ofImport "default" "react-foo" { height = 25 } []
146 | let foo2 = ofImport "default" "react-foo" { height = 50 } []
147 | ```
148 |
149 | ### 4. Use the creation function in your view code
150 |
151 | The function you declared in step 2 now behaves just like any other React component function.
152 |
153 | To use the component in a [Fable-Elmish](https://fable-elmish.github.io/elmish/) view function:
154 |
155 | ```fsharp
156 | let view (model : Model) (dispatch : Msg -> unit) =
157 | div
158 | []
159 | [ progressLine [ Percent model.currentProgress; StrokeColor "red" ] [] ]
160 | ```
161 |
162 | ### 5. Get component state back into your code
163 |
164 | If you want to get from your component state back in F# code, you need to follow react documentation :
165 | https://reactjs.org/docs/lifting-state-up.html to propagate component state to upper components.
166 |
167 | Insert a function in your props to get state back.
168 | ```fsharp
169 | // Define function in props
170 | type ComponentProps =
171 | | GetState of (DateTime -> unit)
172 | | ...
173 |
174 | // sample function matching props signature
175 | let logDateTimeSelected (d : DateTime) =
176 | printfn "Date : %A" d
177 |
178 | // Component definition (here a calendar)
179 | let inline Calendar (props : ComponentProps list) : Fable.React.ReactElement =
180 | ofImport "default" "./CalendarComponent.tsx" (keyValueList CaseRules.LowerFirst props) []
181 |
182 | // Component initialization with props
183 | div [] [
184 | Calendar [ GetState logDateTimeSelected ]
185 | ]
186 | ```
187 |
188 | On Javascript/TypeScript side, you need to use this props within your component
189 | ```js
190 | // Define component props
191 | export interface IComponentProps {
192 | // new props matching F# ComponentProps definition
193 | getState : ((date : Date) => void)
194 | ...
195 | }
196 |
197 | // within JS/TS event handler use your props function
198 | private _onSelectDate(date: Date): void {
199 | this.props.getState(date)
200 | ...
201 | }
202 | ```
203 |
204 | ## Importing using a record
205 |
206 | This is similar to the approach above, but instead of declaring a DU you create a record. Using a record 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.
207 |
208 | ```fsharp
209 | type ProgressProps =
210 | { percent : int
211 | strokeWidth : int
212 | strokeColor : string
213 | }
214 |
215 | let inline progressLine (props : ProgressProps) (elems : ReactElement list) : ReactElement =
216 | ofImport "Line" "rc-progress" props elems
217 | ```
218 |
219 | ## Untyped props
220 |
221 | 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](https://fable.io/docs/communicate/js-from-fable.html#Plain-Old-JavaScript-Objects) module to make `(string * obj)` tuple creation easier to read.
222 |
223 | ```fsharp
224 | open Fable.Core.JsInterop
225 |
226 | ofImport "Line" "rc-progress" (createObj ["strokeWidth" ==> 5]) []
227 |
228 | // You can also use anonymous records
229 | ofImport "Line" "rc-progress" {| strokeWidth = 5 |} []
230 | ```
231 |
232 | ## Edge cases
233 |
234 | 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!
235 |
--------------------------------------------------------------------------------
/fable_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fable-compiler/fable-react/7ca5a7f9645e578aefcf244244047137aba36b13/fable_logo.png
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "9.0.200",
4 | "rollForward": "latestMinor"
5 | }
6 | }
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fable-react",
3 | "lockfileVersion": 2,
4 | "requires": true,
5 | "packages": {}
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "publish": "dotnet fsi build.fsx publish",
5 | "ssrsample-start": "webpack-dev-server --config SSRSample/webpack.config.js",
6 | "ssrsample-build": "webpack --config SSRSample/webpack.config.js",
7 | "ssrsample-build-lib": "fable-splitter SSRSample/src/Client/Client.fsproj -o SSRSample/src/Client/bin/lib --debug --commonjs"
8 | },
9 | "dependencies": {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Fable.React.Types/Fable.React.Extensions.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Fable.React.Extensions
3 |
4 | open Fable.Core
5 |
6 | type Browser.Types.Event with
7 | /// Access the value from target
8 | /// Equivalent to `(this.target :?> HTMLInputElement).value`
9 | []
10 | member this.Value: string =
11 | (this.target :?> Browser.Types.HTMLInputElement).value
12 |
13 | /// Access the checked property from target
14 | /// Equivalent to `(this.target :?> HTMLInputElement).checked`
15 | []
16 | member this.Checked: bool =
17 | (this.target :?> Browser.Types.HTMLInputElement).``checked``
18 |
--------------------------------------------------------------------------------
/src/Fable.React.Types/Fable.React.Hooks.fs:
--------------------------------------------------------------------------------
1 | namespace Fable.React
2 |
3 | open Fable.Core
4 |
5 | type IStateHook<'T> =
6 | []
7 | abstract current: 'T
8 | []
9 | abstract update: 'T -> unit
10 | []
11 | abstract update: ('T -> 'T) -> unit
12 |
13 | // Alias kept for backwards compatibility
14 | type IRefHook<'T> = IRefValue<'T>
15 |
16 | type IReducerHook<'State,'Msg> =
17 | []
18 | abstract current: 'State
19 | []
20 | abstract update: 'Msg -> unit
21 |
22 | type ITransitionHook =
23 | []
24 | abstract isPending: bool
25 | []
26 | abstract startTransition: callback: (unit -> unit) -> unit
27 |
28 | type IHooks =
29 | /// Returns the current state with a function to update it.
30 | /// More info at https://reactjs.org/docs/hooks-reference.html#usestate
31 | abstract useState: initialState: 'T -> IStateHook<'T>
32 |
33 | /// Returns the current state with a function to update it.
34 | /// More info at https://reactjs.org/docs/hooks-reference.html#usestate
35 | []
36 | abstract useStateLazy: initialState: (unit->'T) -> IStateHook<'T>
37 |
38 | /// Accepts a function that contains imperative, possibly effectful code.
39 | /// More info at https://reactjs.org/docs/hooks-reference.html#useeffect
40 | abstract useEffect: effect: (unit->unit) * ?dependencies: obj[] -> unit
41 |
42 | /// Accepts a function that contains imperative, possibly effectful code.
43 | /// The signature is identical to useEffect, but it fires synchronously after
44 | /// all DOM mutations. Use this to read layout from the DOM and synchronously
45 | /// re-render. Updates scheduled inside useLayoutEffect will be flushed
46 | /// synchronously, before the browser has a chance to paint.
47 | /// More info at https://reactjs.org/docs/hooks-reference.html#uselayouteffect
48 | abstract useLayoutEffect: effect: (unit->unit) * ?dependencies: obj[] -> unit
49 |
50 | /// Accepts a function that contains effectful code and returns a disposable for clean-up
51 | /// More info at https://reactjs.org/docs/hooks-reference.html#useeffect
52 | [ {
53 | const disp = $1();
54 | return () => disp.Dispose();
55 | }{{, $2}})""")>]
56 | abstract useEffectDisposable: effect: (unit->System.IDisposable) * ?dependencies: obj[] -> unit
57 |
58 | // abstract useCallback (callback: unit -> unit, dependencies: obj[]): unit -> unit
59 |
60 | /// Accepts a "create" function and an array of dependencies and returns a memoized value
61 | /// More info at https://reactjs.org/docs/hooks-reference.html#usememo
62 | abstract useMemo: callback: (unit->'T) * dependencies: obj[] -> 'T
63 |
64 | /// The returned object will persist for the full lifetime of the component.
65 | /// More info at https://reactjs.org/docs/hooks-reference.html#useref
66 | abstract useRef: initialValue: 'T -> IRefValue<'T>
67 |
68 | /// Accepts a context object (the value returned from createContext) and
69 | /// returns the current context value for that context. The current context
70 | /// value is determined by the value prop of the nearest
71 | /// above the calling component in the tree.
72 | /// More info at https://reactjs.org/docs/hooks-reference.html#usecontext
73 | abstract useContext: ctx: IContext<'T> -> 'T
74 |
75 | /// Display a label for custom hooks in React DevTools.
76 | /// More info at https://reactjs.org/docs/hooks-reference.html#usedebugvalue
77 | abstract useDebugValue: label: string -> unit
78 |
79 | /// Defers formatting of debug value until the Hook is actually inspected
80 | /// More info at https://reactjs.org/docs/hooks-reference.html#usedebugvalue
81 | abstract useDebugValue: value: 'T * format: ('T->string) -> unit
82 |
83 | /// An alternative to useState. Accepts a reducer of type (state, action) => newState, and returns the current state paired with a dispatch method.
84 | /// More info at https://reactjs.org/docs/hooks-reference.html#usereducer
85 | abstract useReducer: reducer: ('State -> 'Msg -> 'State) * initialState: 'State -> IReducerHook<'State, 'Msg>
86 |
87 | /// An alternative to useState. Accepts a reducer of type (state, action) => newState, and returns the current state paired with a dispatch method.
88 | /// More info at https://reactjs.org/docs/hooks-reference.html#usereducer
89 | abstract useReducer: reducer: ('State -> 'Msg -> 'State) * initialArg: 'I * init: ('I -> 'State) -> IReducerHook<'State, 'Msg>
90 |
91 | /// Returns a stateful value for the pending state of the transition, and a function to start it.
92 | /// More info at https://reactjs.org/docs/hooks-reference.html#usetransition
93 | /// Requires React 18.
94 | abstract useTransition: unit -> ITransitionHook
95 |
96 | /// useId is a hook for generating unique IDs that are stable across the server and client, while avoiding hydration mismatches.
97 | /// More info at https://reactjs.org/docs/hooks-reference.html#useid
98 | /// Requires React 18.
99 | abstract useId: unit -> string
100 |
101 | /// useDeferredValue accepts a value and returns a new copy of the value that will defer to more urgent updates. If the current render is the result of an urgent update, like user input, React will return the previous value and then render the new value after the urgent render has completed.
102 | /// More info at https://reactjs.org/docs/hooks-reference.html#usedeferredvalue
103 | /// Requires React 18.
104 | abstract useDeferredValue: 'T -> 'T
105 |
106 | []
107 | module HookBindings =
108 | let private makeDummyStateHook value =
109 | { new IStateHook<'T> with
110 | member __.current = value
111 | member __.update(x: 'T) = ()
112 | member __.update(f: 'T->'T) = () }
113 |
114 | let private makeDummyReducerHook state =
115 | { new IReducerHook<'State,'Msg> with
116 | member __.current = state
117 | member __.update(msg: 'Msg) = () }
118 |
119 | let private makeDummyTransitionHook () =
120 | { new ITransitionHook with
121 | member __.isPending = false
122 | member __.startTransition callback = () }
123 |
124 | #if FABLE_REPL_LIB
125 | []
126 | #else
127 | []
128 | #endif
129 | let Hooks: IHooks =
130 | // Placeholder for SSR
131 | { new IHooks with
132 | member __.useState(initialState: 'T) =
133 | makeDummyStateHook initialState
134 | member __.useStateLazy(initialState) =
135 | makeDummyStateHook (initialState())
136 | member __.useEffect(effect, dependencies) = ()
137 | member __.useEffectDisposable(effect, dependencies) = ()
138 | member __.useMemo(callback, dependencies) = callback()
139 | member __.useRef(initialValue) =
140 | { new IRefValue<_> with
141 | member __.current with get() = initialValue and set _ = () }
142 | member __.useContext ctx =
143 | (ctx :?> ISSRContext<_>).DefaultValue
144 | member __.useDebugValue(label): unit = ()
145 | member __.useDebugValue(value, format): unit = ()
146 | member __.useReducer(reducer,initialState) = makeDummyReducerHook initialState
147 | member __.useReducer(reducer, initialArgument, init) = makeDummyReducerHook (init initialArgument)
148 | member __.useTransition() = makeDummyTransitionHook()
149 | member __.useDeferredValue value = value
150 | member __.useId() = ""
151 | member __.useLayoutEffect(effect, dependencies) = ()
152 | }
--------------------------------------------------------------------------------
/src/Fable.React.Types/Fable.React.Types.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 19.0.0
5 | 19.0.0-alpha.1
6 | netstandard2.0
7 | enable
8 |
9 | fsharp;fable;javascript;f#;js;react;fable-binding;fable-javascript
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/Fable.React.Types/Fable.React.fs:
--------------------------------------------------------------------------------
1 | namespace Fable.React
2 |
3 | open System
4 | open Fable.Core
5 |
6 | // The import statement on the type interface are here so
7 | // people using Fable => TypeScript can get the correct types in the output
8 | // without having to use hack via `unbox` in their code
9 |
10 | []
11 | type [] ReactElement =
12 | interface end
13 |
14 | []
15 | type ReactElementType =
16 | interface end
17 |
18 | []
19 | type ReactElementType<'props> =
20 | inherit ReactElementType
21 |
22 | type IRefValue<'T> =
23 | abstract current: 'T with get, set
24 |
25 | type IContext<'T> =
26 | interface end
27 |
28 | type ISSRContext<'T> =
29 | inherit IContext<'T>
30 | abstract DefaultValue: 'T
31 |
32 | type IReactExports =
33 | /// Create and return a new React element of the given type. The type argument can be either a tag name string (such as 'div' or 'span'), a React component type (a class or a function), or a React fragment type.
34 | abstract createElement: comp: obj * props: objnull * [] children: ReactElement seq -> ReactElement
35 |
36 | /// Creates a Context object. When React renders a component that subscribes to this Context object it will read the current context value from the closest matching Provider above it in the tree.
37 | abstract createContext: defaultValue: 'T -> IContext<'T>
38 |
39 | /// React.createRef creates a ref that can be attached to React elements via the ref attribute.
40 | abstract createRef: initialValue: 'T -> IRefValue<'T>
41 |
42 | /// React.forwardRef creates a React component that forwards the ref attribute it receives to another component below in the tree.
43 | abstract forwardRef: fn: ('props -> IRefValue<'T> option -> ReactElement) -> ReactElementType<'props>
44 |
45 | /// If your component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.
46 | abstract memo: render: ('props -> ReactElement) * areEqual: ('props -> 'props -> bool) -> ReactElementType<'props>
47 |
48 | /// The React.Fragment component lets you return multiple elements in a render() method without creating an additional DOM element.
49 | abstract Fragment: ReactElementType
50 |
51 | /// React.Suspense lets you specify the loading indicator in case some components in the tree below it are not yet ready to render. In the future we plan to let Suspense handle more scenarios such as data fetching.
52 | abstract Suspense: ReactElementType
53 |
54 | /// React.lazy() lets you define a component that is loaded dynamically. This helps reduce the bundle size to delay loading components that aren’t used during the initial render.
55 | abstract ``lazy``: f: (unit -> JS.Promise<'TIn>) -> 'TOut
56 |
57 | /// React.startTransition lets you mark updates inside the provided callback as transitions. This method is designed to be used when React.useTransition is not available.
58 | /// Requires React 18.
59 | abstract startTransition: callback: (unit -> unit) -> unit
60 |
61 | /// The React version.
62 | abstract version: string
63 |
64 | module ReactBindings =
65 | /// Mainly intended for internal use
66 | #if FABLE_REPL_LIB
67 | []
68 | #else
69 | []
70 | #endif
71 | let React: IReactExports = jsNative
72 |
73 | /// Create a React component by inheriting this class as follows
74 | ///
75 | /// ```
76 | /// type MyComponent(initialProps) =
77 | /// inherit React.Component(initialProps)
78 | /// base.setInitState({ value = 5 })
79 | ///
80 | /// override this.render() =
81 | /// // Don't use captured `initialProps` from constructor,
82 | /// // use `this.props` instead (updated version)
83 | /// let msg = sprintf "Hello %s, you have %i €"
84 | /// this.props.name this.state.value
85 | /// div [] [ofString msg]
86 | /// ```
87 | type [] Component<'P,'S>(initProps: 'P) =
88 | []
89 | member __.props: 'P = initProps
90 |
91 | []
92 | member val children: ReactElement array = [| |] with get, set
93 |
94 | []
95 | member val state: 'S = Unchecked.defaultof<'S> with get, set
96 |
97 | /// ATTENTION: Within the constructor, use `setInitState`
98 | /// Enqueues changes to the component state and tells React that this component and its children need to be re-rendered with the updated state. This is the primary method you use to update the user interface in response to event handlers and server responses.
99 | /// Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
100 | /// setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
101 | /// setState() will always lead to a re-render unless shouldComponentUpdate() returns false. If mutable objects are being used and conditional rendering logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.
102 | ['P->'S) instead.")>]
103 | []
104 | member x.setState(value: 'S): unit = x.state <- value
105 |
106 | /// Overload of `setState` accepting updater function with the signature: `(prevState, props) => stateChange`
107 | /// prevState is a reference to the previous state. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from prevState and props.
108 | /// Both prevState and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with prevState.
109 | []
110 | member x.setState(updater: 'S->'P->'S): unit = x.state <- updater x.state x.props
111 |
112 | /// This method can only be called in the constructor
113 | []
114 | member x.setInitState(value: 'S): unit = x.state <- value
115 |
116 | /// By default, when your component’s state or props change, your component will re-render. If your render() method depends on some other data, you can tell React that the component needs re-rendering by calling forceUpdate().
117 | /// Calling forceUpdate() will cause render() to be called on the component, skipping shouldComponentUpdate(). This will trigger the normal lifecycle methods for child components, including the shouldComponentUpdate() method of each child. React will still only update the DOM if the markup changes.
118 | /// Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render().
119 | []
120 | member __.forceUpdate(?callBack: unit->unit): unit = ()
121 |
122 | []
123 | member __.isMounted(): bool = false
124 |
125 | /// Invoked immediately before mounting occurs. It is called before render(), therefore calling setState() synchronously in this method will not trigger an extra rendering. Generally, we recommend using the constructor() instead.
126 | /// Avoid introducing any side-effects or subscriptions in this method. For those use cases, use componentDidMount() instead.
127 | /// This is the only lifecycle hook called on server rendering.
128 | abstract componentWillMount: unit -> unit
129 | default __.componentWillMount () = ()
130 |
131 | /// Invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
132 | /// This method is a good place to set up any subscriptions. If you do that, don’t forget to unsubscribe in componentWillUnmount().
133 | /// Calling setState() in this method will trigger an extra rendering, but it is guaranteed to flush during the same tick. This guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state. Use this pattern with caution because it often causes performance issues. It can, however, be necessary for cases like modals and tooltips when you need to measure a DOM node before rendering something that depends on its size or position.
134 | abstract componentDidMount: unit -> unit
135 | default __.componentDidMount () = ()
136 |
137 | /// Invoked before a mounted component receives new props. If you need to update the state in response to prop changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions using this.setState() in this method.
138 | /// Note that React may call this method even if the props have not changed, so make sure to compare the current and next values if you only want to handle changes. This may occur when the parent component causes your component to re-render.
139 | /// React doesn’t call componentWillReceiveProps() with initial props during mounting. It only calls this method if some of component’s props may update. Calling this.setState() generally doesn’t trigger componentWillReceiveProps().
140 | abstract componentWillReceiveProps: nextProps: 'P -> unit
141 | default __.componentWillReceiveProps (_) = ()
142 |
143 | /// Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.
144 | /// shouldComponentUpdate() is invoked before rendering when new props or state are being received. Defaults to true. This method is not called for the initial render or when forceUpdate() is used.
145 | /// Returning false does not prevent child components from re-rendering when their state changes.
146 | /// Currently, if shouldComponentUpdate() returns false, then componentWillUpdate(), render(), and componentDidUpdate() will not be invoked. Note that in the future React may treat shouldComponentUpdate() as a hint rather than a strict directive, and returning false may still result in a re-rendering of the component.
147 | /// If you determine a specific component is slow after profiling, you may change it to inherit from React.PureComponent which implements shouldComponentUpdate() with a shallow prop and state comparison. If you are confident you want to write it by hand, you may compare this.props with nextProps and this.state with nextState and return false to tell React the update can be skipped.
148 | /// We do not recommend doing deep equality checks or using JSON.stringify() in shouldComponentUpdate(). It is very inefficient and will harm performance.
149 | abstract shouldComponentUpdate: nextProps: 'P * nextState: 'S -> bool
150 | default __.shouldComponentUpdate (_, _) = true
151 |
152 | /// Invoked immediately before rendering when new props or state are being received. Use this as an opportunity to perform preparation before an update occurs. This method is not called for the initial render.
153 | /// Note that you cannot call this.setState() here; nor should you do anything else (e.g. dispatch a Redux action) that would trigger an update to a React component before componentWillUpdate() returns.
154 | /// If you need to update state in response to props changes, use componentWillReceiveProps() instead.
155 | /// > componentWillUpdate() will not be invoked if shouldComponentUpdate() returns false.
156 | abstract componentWillUpdate: nextProps: 'P * nextState: 'S -> unit
157 | default __.componentWillUpdate (_, _) = ()
158 |
159 | /// Invoked immediately after updating occurs. This method is not called for the initial render.
160 | /// Use this as an opportunity to operate on the DOM when the component has been updated. This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).
161 | /// > componentDidUpdate() will not be invoked if shouldComponentUpdate() returns false.
162 | abstract componentDidUpdate: prevProps: 'P * prevState: 'S -> unit
163 | default __.componentDidUpdate (_, _) = ()
164 |
165 | /// Invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount().
166 | abstract componentWillUnmount: unit -> unit
167 | default __.componentWillUnmount () = ()
168 |
169 | /// Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.
170 | /// A class component becomes an error boundary if it defines this lifecycle method. Calling setState() in it lets you capture an unhandled JavaScript error in the below tree and display a fallback UI. Only use error boundaries for recovering from unexpected exceptions; don’t try to use them for control flow.
171 | /// For more details, see [Error Handling in React 16](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html).
172 | /// > Error boundaries only catch errors in the components below them in the tree. An error boundary can’t catch an error within itself.
173 | abstract componentDidCatch: error: Exception * info: obj -> unit
174 | default __.componentDidCatch (_, _) = ()
175 |
176 | /// This function should be pure, meaning that it does not modify component state, it returns the same result each time it’s invoked, and it does not directly interact with the browser. If you need to interact with the browser, perform your work in componentDidMount() or the other lifecycle methods instead. Keeping render() pure makes components easier to think about.
177 | /// > render() will not be invoked if shouldComponentUpdate() returns false.
178 | abstract render: unit -> ReactElement
179 |
180 | interface ReactElement
181 |
182 | /// A react component that implements `shouldComponentUpdate()` with a shallow prop and state comparison.
183 | ///
184 | /// Usage:
185 | /// ```
186 | /// type MyComponent(initialProps) =
187 | /// inherit React.PureComponent(initialProps)
188 | /// base.setInitState({ value = 5 })
189 | /// override this.render() =
190 | /// let msg = sprintf "Hello %s, you have %i €"
191 | /// this.props.name this.state.value
192 | /// div [] [ofString msg]
193 | /// ```
194 | type [] PureComponent<'P, 'S>(props: 'P) =
195 | inherit Component<'P, 'S>(props)
196 |
197 | /// A react component that implements `shouldComponentUpdate()` with a shallow prop comparison.
198 | ///
199 | /// Usage:
200 | /// ```
201 | /// type MyComponent(initialProps) =
202 | /// inherit React.PureStatelessComponent(initialProps)
203 | /// override this.render() =
204 | /// let msg = sprintf "Hello %s, you have %i €"
205 | /// this.props.name this.props.value
206 | /// div [] [ofString msg]
207 | /// ```
208 | type [] PureStatelessComponent<'P>(props: 'P) =
209 | inherit Component<'P, obj>(props)
210 |
211 | type FragmentProps = { key: string }
212 |
213 | type [] Fragment(props: FragmentProps) =
214 | interface ReactElement
215 |
216 | // These are not React interfaces but we add them here in case other Fable libraries need them
217 |
218 | type IProp =
219 | interface end
220 |
221 | type IHTMLProp =
222 | inherit IProp
223 |
224 | type IFragmentProp =
225 | inherit IProp
226 |
--------------------------------------------------------------------------------
/src/Fable.React.Types/RELEASE_NOTES.md:
--------------------------------------------------------------------------------
1 | ### 19.0.0-alpha.1
2 |
3 | - Upgrade `FSharp.Core` to 9.0.100 to support F# nullness
4 | - Make minimal adaptation to support F# nullness (`createElement` `props` become `objnull` instead of `obj`)
5 | - Compile will Nullable enabled
6 |
7 | ### 18.4.0
8 |
9 | - Add `Import` attribute to the React type definition allowing better type system when targeting TypeScript
10 |
11 | ### 18.3.0
12 |
13 | - Add tags for indexing in Fable packages project
14 |
15 | ### 18.2.0
16 |
17 | - Include source files under `fable` folder
18 |
19 | ### 18.1.0
20 |
21 | - Move react-dom bindings to own package
22 |
23 | ### 18.0.0
24 |
25 | - React 18 types
26 |
--------------------------------------------------------------------------------
/src/Fable.React/Fable.React.FunctionComponent.fs:
--------------------------------------------------------------------------------
1 | namespace Fable.React
2 |
3 | open System
4 | open System.Runtime.CompilerServices
5 | open Fable.Core
6 | open Fable.Core.JsInterop
7 |
8 | #if FABLE_COMPILER
9 | type FunctionComponentPreparedRenderFunctionCache() =
10 | static let cache =
11 | let cache = JS.Constructors.Map.Create()
12 | #if DEBUG
13 | // Clear the cache when HMR is fired
14 | FunctionComponentPreparedRenderFunctionCache.OnHMR(fun () -> cache.clear())
15 | #endif
16 | cache
17 |
18 | static member GetOrAdd(
19 | cacheKey: string,
20 | displayName: string,
21 | render: 'Props -> ReactElement,
22 | memoizeWith: ('Props -> 'Props -> bool) option,
23 | withKey: ('Props -> string) option,
24 | [] ?__callingMemberName: string) =
25 | let prepareRenderFunction () =
26 | render?displayName <- displayName
27 | let elemType =
28 | match memoizeWith with
29 | | Some areEqual ->
30 | #if DEBUG
31 | // In development mode, force rerenders always when HMR is fired
32 | let areEqual x y =
33 | not FunctionComponentPreparedRenderFunctionCache.IsHMRApplied && areEqual x y
34 | #endif
35 | let memoElement = ReactElementType.memoWith areEqual render
36 | memoElement?displayName <- "Memo(" + displayName + ")"
37 | memoElement
38 | | None -> ReactElementType.ofFunction render
39 | fun props ->
40 | let props =
41 | match withKey with
42 | | Some f -> props?key <- f props; props
43 | | None -> props
44 | ReactElementType.create elemType props []
45 |
46 | if cache.has(cacheKey) then
47 | cache.get(cacheKey) :?> ('Props -> ReactElement)
48 | else
49 | let v = prepareRenderFunction ()
50 | cache.set(cacheKey, box v) |> ignore
51 | v
52 |
53 | [ { if (status === 'apply') $0(); })
57 | : void 0""")>]
58 | static member OnHMR(callback: unit->unit): unit = jsNative
59 |
60 | []
64 | static member IsHMRApplied: bool = jsNative
65 | #endif
66 |
67 | type FunctionComponent =
68 | #if !FABLE_REPL_LIB
69 | /// Creates a lazy React component from a function in another file
70 | /// ATTENTION: Requires fable-compiler 2.3 or above
71 | /// Pass the external reference directly into the argument (avoid pipes)
72 | static member inline Lazy(f: 'Props -> ReactElement, fallback: ReactElement): 'Props -> ReactElement =
73 | #if FABLE_COMPILER
74 | let elemType = ReactBindings.React.``lazy``(fun () ->
75 | // React.lazy requires a default export
76 | (importValueDynamic f).``then``(fun x -> createObj ["default" ==> x]))
77 | fun props ->
78 | ReactElementType.create
79 | ReactBindings.React.Suspense
80 | (createObj ["fallback" ==> fallback])
81 | [ReactElementType.create elemType props []]
82 | #else
83 | f // No React.lazy for SSR, just return component as is
84 | #endif
85 | #endif
86 |
87 | /// Creates a function React component that can use hooks to manage the component's life cycle,
88 | /// and is displayed in React dev tools (use `displayName` to customize the name).
89 | /// Uses React.memo if `memoizeWith` is specified (check `equalsButFunctions` and `memoEqualsButFunctions` helpers).
90 | /// When you need a key to optimize collections in React you can use `withKey` argument or define a `key` field in the props object.
91 | static member inline Of(render: 'Props->ReactElement,
92 | ?displayName: string,
93 | ?memoizeWith: 'Props -> 'Props -> bool,
94 | ?withKey: 'Props -> string
95 | #if FABLE_COMPILER
96 | ,[] ?__callingMemberName: string
97 | ,[] ?__callingSourceFile: string
98 | ,[] ?__callingSourceLine: int
99 | #endif
100 | ): 'Props -> ReactElement =
101 | #if FABLE_COMPILER
102 | // Cache the render function to prevent recreating the component every time when FunctionComponent.Of
103 | // is called inside another function (including generic values: let MyCom<'T> = ...)
104 | let cacheKey =
105 | __callingSourceFile.Value +
106 | "#L" + (string __callingSourceLine.Value) +
107 | // direct caller can also be generic, need separate cached func per 'Props argument
108 | ";" + typeof<'Props>.FullName
109 | let displayName = defaultArg displayName __callingMemberName.Value
110 |
111 | FunctionComponentPreparedRenderFunctionCache.GetOrAdd (cacheKey, displayName, render, memoizeWith, withKey)
112 | #else
113 | let elemType = ReactElementType.ofFunction render
114 | fun props ->
115 | ReactElementType.create elemType props []
116 | #endif
117 |
--------------------------------------------------------------------------------
/src/Fable.React/Fable.React.Helpers.fs:
--------------------------------------------------------------------------------
1 | namespace Fable.React
2 |
3 | open Fable.Core
4 | open Fable.Core.JsInterop
5 | open Browser
6 | open Props
7 |
8 | #if !FABLE_COMPILER
9 | type HTMLNode =
10 | | Text of string
11 | | RawText of string
12 | | Node of string * IProp seq * ReactElement seq
13 | | List of ReactElement seq
14 | | Empty
15 | with interface ReactElement
16 |
17 | type ServerElementType =
18 | | Tag
19 | | Fragment
20 | | Component
21 |
22 | type ReactElementTypeWrapper<'P> =
23 | | Comp of obj
24 | | Fn of ('P -> ReactElement)
25 | | HtmlTag of string
26 | interface ReactElementType<'P>
27 |
28 | []
29 | module ServerRendering =
30 | let [] private ChildrenName = "children"
31 |
32 | let private createServerElementPrivate(tag: obj, props: obj, children: ReactElement seq, elementType: ServerElementType) =
33 | match elementType with
34 | | ServerElementType.Tag ->
35 | HTMLNode.Node (string tag, props :?> IProp seq, children) :> ReactElement
36 | | ServerElementType.Fragment ->
37 | HTMLNode.List children :> ReactElement
38 | | ServerElementType.Component ->
39 | let tag = tag :?> System.Type
40 | let comp = System.Activator.CreateInstance(tag, props)
41 | let childrenProp = tag.GetProperty(ChildrenName)
42 | childrenProp.SetValue(comp, children |> Seq.toArray)
43 | let render = tag.GetMethod("render")
44 | render.Invoke(comp, null) :?> ReactElement
45 |
46 | let private createServerElementByFnPrivate(f, props, children) =
47 | let propsType = props.GetType()
48 | let props =
49 | if propsType.GetProperty (ChildrenName) |> isNull then
50 | props
51 | else
52 | let values = ResizeArray ()
53 | let properties = propsType.GetProperties()
54 | for p in properties do
55 | if p.Name = ChildrenName then
56 | values.Add (children |> Seq.toArray)
57 | else
58 | values.Add (FSharp.Reflection.FSharpValue.GetRecordField(props, p))
59 | FSharp.Reflection.FSharpValue.MakeRecord(propsType, values.ToArray()) :?> 'P
60 | f props
61 |
62 | // In most cases these functions are inlined (mainly for Fable optimizations)
63 | // so we create a proxy to avoid inlining big functions every time
64 |
65 | let createServerElement(tag: obj, props: obj, children: ReactElement seq, elementType: ServerElementType) =
66 | createServerElementPrivate(tag, props, children, elementType)
67 |
68 | let createServerElementByFn(f, props, children) =
69 | createServerElementByFnPrivate(f, props, children)
70 | #endif
71 |
72 | []
73 | []
74 | module ReactElementType =
75 | let inline ofComponent<'comp, 'props, 'state when 'comp :> Component<'props, 'state>> : ReactElementType<'props> =
76 | #if FABLE_REPL_LIB
77 | failwith "Cannot create React components from types in Fable REPL"
78 | #else
79 | #if FABLE_COMPILER
80 | jsConstructor<'comp> |> unbox
81 | #else
82 | Comp (typeof<'comp>) :> _
83 | #endif
84 | #endif
85 |
86 | let inline ofFunction<'props> (f: 'props -> ReactElement): ReactElementType<'props> =
87 | #if FABLE_COMPILER
88 | f |> unbox
89 | #else
90 | Fn f :> _
91 | #endif
92 |
93 | let inline ofHtmlElement<'props> (name: string): ReactElementType<'props> =
94 | #if FABLE_COMPILER
95 | unbox name
96 | #else
97 | HtmlTag name :> ReactElementType<'props>
98 | #endif
99 |
100 | /// Create a ReactElement to be rendered from an element type, props and children
101 | let inline create<'props> (comp: ReactElementType<'props>) (props: 'props) (children: ReactElement seq): ReactElement =
102 | #if FABLE_COMPILER
103 | ReactBindings.React.createElement(comp, props, children)
104 | #else
105 | match (comp :?> ReactElementTypeWrapper<'props>) with
106 | | Comp obj -> ServerRendering.createServerElement(obj, props, children, ServerElementType.Component)
107 | | Fn f -> ServerRendering.createServerElementByFn(f, props, children)
108 | | HtmlTag obj -> ServerRendering.createServerElement(obj, props, children, ServerElementType.Tag)
109 | #endif
110 |
111 | /// React.memo is a higher order component. It’s similar to React.PureComponent but for function components instead of classes.
112 | /// If your function component renders the same result given the same props, you can wrap it in a call to React.memo.
113 | /// React will skip rendering the component, and reuse the last rendered result.
114 | /// By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can use `memoWith`.
115 | let memo<'props> (render: 'props -> ReactElement) =
116 | #if FABLE_COMPILER
117 | ReactBindings.React.memo(render, unbox null)
118 | #else
119 | ofFunction render
120 | #endif
121 |
122 | /// React.memo is a higher order component. It’s similar to React.PureComponent but for function components instead of classes.
123 | /// If your function renders the same result given the "same" props (according to `areEqual`), you can wrap it in a call to React.memo.
124 | /// React will skip rendering the component, and reuse the last rendered result.
125 | /// By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can use `memoWith`.
126 | /// This version allow you to control the comparison used instead of the default shallow one by provide a custom comparison function.
127 | let memoWith<'props> (areEqual: 'props -> 'props -> bool) (render: 'props -> ReactElement) =
128 | #if FABLE_COMPILER
129 | ReactBindings.React.memo(render, areEqual)
130 | #else
131 | ofFunction render
132 | #endif
133 |
134 |
135 | []
136 | module Helpers =
137 | []
138 | let inline createElement(comp: obj, props: obj, [] children: ReactElement seq): ReactElement =
139 | #if FABLE_COMPILER
140 | ReactBindings.React.createElement(comp, props, children)
141 | #else
142 | HTMLNode.Empty :> _
143 | #endif
144 |
145 | /// Instantiate a component from a type inheriting React.Component
146 | /// Example: `ofType { myProps = 5 } []`
147 | let inline ofType<'T,'P,'S when 'T :> Component<'P,'S>> (props: 'P) (children: ReactElement seq): ReactElement =
148 | ReactElementType.create ReactElementType.ofComponent<'T,_,_> props children
149 |
150 | []
151 | let inline com<'T,'P,'S when 'T :> Component<'P,'S>> (props: 'P) (children: ReactElement seq): ReactElement =
152 | ofType<'T, 'P, 'S> props children
153 |
154 | let inline ofFunction<'P> (f: 'P -> ReactElement) (props: 'P) (children: ReactElement seq): ReactElement =
155 | ReactElementType.create (ReactElementType.ofFunction f) props children
156 |
157 | /// Instantiate an imported React component. The first two arguments must be string literals, "default" can be used for the first one.
158 | /// Example: `ofImport "Map" "leaflet" { x = 10; y = 50 } []`
159 | let inline ofImport<'P> (importMember: string) (importPath: string) (props: 'P) (children: ReactElement seq): ReactElement =
160 | #if FABLE_REPL_LIB
161 | failwith "Cannot import React components in Fable REPL"
162 | #else
163 | #if FABLE_COMPILER
164 | ReactBindings.React.createElement(import importMember importPath, props, children)
165 | #else
166 | failwith "Cannot import React components in .NET"
167 | #endif
168 | #endif
169 |
170 | #if FABLE_COMPILER
171 | []
172 | let private isFunction (x: obj): bool = jsNative
173 |
174 | []
175 | let private isNonEnumerableObject (x: obj): bool = jsNative
176 | #endif
177 |
178 | /// Normal structural F# comparison, but ignores top-level functions (e.g. Elmish dispatch).
179 | /// Can be used e.g. with the `FunctionComponent.Of` `memoizeWith` parameter.
180 | let equalsButFunctions (x: 'a) (y: 'a) =
181 | #if FABLE_COMPILER
182 | if obj.ReferenceEquals(x, y) then
183 | true
184 | elif isNonEnumerableObject x && not(isNull(box y)) then
185 | let keys = JS.Constructors.Object.keys x
186 | let length = keys.Count
187 | let mutable i = 0
188 | let mutable result = true
189 | while i < length && result do
190 | let key = keys.[i]
191 | i <- i + 1
192 | let xValue = x?(key)
193 | result <- isFunction xValue || xValue = y?(key)
194 | result
195 | else
196 | (box x) = (box y)
197 | #else
198 | // Server rendering, won't be actually used
199 | // Avoid `x = y` because it will force 'a to implement structural equality
200 | false
201 | #endif
202 |
203 | /// Comparison similar to default React.memo, but ignores functions (e.g. Elmish dispatch).
204 | /// Performs a memberwise comparison where value types and strings are compared by value,
205 | /// and other types by reference.
206 | /// Can be used e.g. with the `FunctionComponent.Of` `memoizeWith` parameter.
207 | let memoEqualsButFunctions (x: 'a) (y: 'a) =
208 | #if FABLE_COMPILER
209 | if obj.ReferenceEquals(x, y) then
210 | true
211 | elif isNonEnumerableObject x && not(isNull(box y)) then
212 | let keys = JS.Constructors.Object.keys x
213 | let length = keys.Count
214 | let mutable i = 0
215 | let mutable result = true
216 | while i < length && result do
217 | let key = keys.[i]
218 | i <- i + 1
219 | let xValue = x?(key)
220 | result <- isFunction xValue || obj.ReferenceEquals(xValue, y?(key))
221 | result
222 | else
223 | false
224 | #else
225 | // Server rendering, won't be actually used
226 | // Avoid `x = y` because it will force 'a to implement structural equality
227 | false
228 | #endif
229 |
230 | []
231 | let memoBuilder<'props> (name: string) (render: 'props -> ReactElement) : 'props -> ReactElement =
232 | #if FABLE_COMPILER
233 | render?displayName <- name
234 | #endif
235 | let memoType = ReactElementType.memo render
236 | fun props ->
237 | ReactElementType.create memoType props []
238 |
239 | []
240 | let memoBuilderWith<'props> (name: string) (areEqual: 'props -> 'props -> bool) (render: 'props -> ReactElement) : 'props -> ReactElement =
241 | #if FABLE_COMPILER
242 | render?displayName <- name
243 | #endif
244 | let memoType = ReactElementType.memoWith areEqual render
245 | fun props ->
246 | ReactElementType.create memoType props []
247 |
248 | []
249 | let inline from<'P> (com: ReactElementType<'P>) (props: 'P) (children: ReactElement seq): ReactElement =
250 | ReactElementType.create com props children
251 |
252 | /// Alias of `ofString`
253 | let inline str (s: string): ReactElement =
254 | #if FABLE_COMPILER
255 | unbox s
256 | #else
257 | HTMLNode.Text s :> ReactElement
258 | #endif
259 |
260 | /// Cast a string to a React element (erased in runtime)
261 | let inline ofString (s: string): ReactElement =
262 | str s
263 |
264 | /// The equivalent of `sprintf (...) |> str`
265 | let inline strf format =
266 | Printf.kprintf str format
267 |
268 | /// Cast an option value to a React element (erased in runtime)
269 | let inline ofOption (o: ReactElement option): ReactElement =
270 | match o with Some o -> o | None -> null // Option.toObj(o)
271 |
272 | []
273 | let opt (o: ReactElement option): ReactElement =
274 | ofOption o
275 |
276 | /// Cast an int to a React element (erased in runtime)
277 | let inline ofInt (i: int): ReactElement =
278 | #if FABLE_COMPILER
279 | unbox i
280 | #else
281 | HTMLNode.RawText (string i) :> ReactElement
282 | #endif
283 |
284 | /// Cast a float to a React element (erased in runtime)
285 | let inline ofFloat (f: float): ReactElement =
286 | #if FABLE_COMPILER
287 | unbox f
288 | #else
289 | HTMLNode.RawText (string f) :> ReactElement
290 | #endif
291 |
292 | /// Returns a list **from .render() method**
293 | let inline ofList (els: ReactElement list): ReactElement =
294 | #if FABLE_COMPILER
295 | unbox(List.toArray els)
296 | #else
297 | HTMLNode.List els :> ReactElement
298 | #endif
299 |
300 | /// Returns an array **from .render() method**
301 | let inline ofArray (els: ReactElement array): ReactElement =
302 | #if FABLE_COMPILER
303 | unbox els
304 | #else
305 | HTMLNode.List els :> ReactElement
306 | #endif
307 |
308 | /// A ReactElement when you don't want to render anything (null in javascript)
309 | let nothing: ReactElement =
310 | #if FABLE_COMPILER
311 | null
312 | #else
313 | HTMLNode.Empty :> ReactElement
314 | #endif
315 |
316 | /// Instantiate a DOM React element
317 | let inline domEl (tag: string) (props: IHTMLProp seq) (children: ReactElement seq): ReactElement =
318 | #if FABLE_COMPILER
319 | ReactBindings.React.createElement(tag, keyValueList CaseRules.LowerFirst props, children)
320 | #else
321 | ServerRendering.createServerElement(tag, (props |> Seq.cast), children, ServerElementType.Tag)
322 | #endif
323 |
324 | /// Instantiate a DOM React element (void)
325 | let inline voidEl (tag: string) (props: IHTMLProp seq) : ReactElement =
326 | #if FABLE_COMPILER
327 | ReactBindings.React.createElement(tag, keyValueList CaseRules.LowerFirst props, [])
328 | #else
329 | ServerRendering.createServerElement(tag, (props |> Seq.cast), [], ServerElementType.Tag)
330 | #endif
331 |
332 | /// Instantiate an SVG React element
333 | let inline svgEl (tag: string) (props: IProp seq) (children: ReactElement seq): ReactElement =
334 | #if FABLE_COMPILER
335 | ReactBindings.React.createElement(tag, keyValueList CaseRules.LowerFirst props, children)
336 | #else
337 | ServerRendering.createServerElement(tag, (props |> Seq.cast), children, ServerElementType.Tag)
338 | #endif
339 |
340 | /// Instantiate a React fragment
341 | let inline fragment (props: IFragmentProp seq) (children: ReactElement seq): ReactElement =
342 | #if FABLE_COMPILER
343 | ReactBindings.React.createElement(ReactBindings.React.Fragment, keyValueList CaseRules.LowerFirst props, children)
344 | #else
345 | ServerRendering.createServerElement(typeof, (props |> Seq.cast), children, ServerElementType.Fragment)
346 | #endif
347 |
348 | /// Accepts a context value to be passed to consuming components that are descendants of this Provider.
349 | /// One Provider can be connected to many consumers. Providers can be nested to override values deeper within the tree.
350 | /// Important: In SSR, this is ignored, and the DEFAULT value is consumed!
351 | /// More info at https://reactjs.org/docs/context.html#contextprovider
352 | let inline contextProvider (ctx: IContext<'T>) (value: 'T) (children: ReactElement seq): ReactElement =
353 | #if FABLE_COMPILER
354 | ReactBindings.React.createElement(ctx?Provider, createObj ["value" ==> value], children)
355 | #else
356 | fragment [] children
357 | #endif
358 |
359 | /// Consumes a context value, either from the nearest parent in the tree, or from the default value.
360 | /// Important: in SSR, this will always consume the context DEFAULT value!
361 | /// More info at https://reactjs.org/docs/context.html#contextconsumer
362 | let inline contextConsumer (ctx: IContext<'T>) (children: 'T -> ReactElement): ReactElement =
363 | #if FABLE_COMPILER
364 | ReactBindings.React.createElement(ctx?Consumer, null, [!!children])
365 | #else
366 | let ctx = ctx :?> ISSRContext<_>
367 | fragment [] [children(ctx.DefaultValue)]
368 | #endif
369 |
370 | /// Creates a Context object. When React renders a component that subscribes to this Context
371 | /// object it will read the current context value from the closest matching Provider above it
372 | /// in the tree. More info at https://reactjs.org/docs/context.html#reactcreatecontext
373 | let inline createContext (defaultValue: 'T): IContext<'T> =
374 | #if FABLE_COMPILER
375 | ReactBindings.React.createContext(defaultValue)
376 | #else
377 | upcast { new ISSRContext<_> with member __.DefaultValue = defaultValue }
378 | #endif
379 |
380 | /// To be used in constructors of class components
381 | /// (for function components use `useRef` hook)
382 | let inline createRef (initialValue: 'T): IRefValue<'T> =
383 | #if FABLE_COMPILER
384 | ReactBindings.React.createRef(initialValue)
385 | #else
386 | { new IRefValue<_> with
387 | member __.current with get() = initialValue and set _ = () }
388 | #endif
389 |
390 | // Class list helpers
391 | let classBaseList baseClass classes =
392 | classes
393 | |> Seq.choose (fun (name, condition) ->
394 | if condition && not(System.String.IsNullOrEmpty(name)) then Some name
395 | else None)
396 | |> Seq.fold (fun state name -> state + " " + name) baseClass
397 | |> ClassName
398 |
399 | let classList classes = classBaseList "" classes
400 |
401 | /// Finds a DOM element by its ID and mounts the React element there
402 | /// Important: Not available in SSR
403 | let inline mountById (domElId: string) (reactEl: ReactElement): unit =
404 | #if FABLE_COMPILER
405 | ReactDom.render(reactEl, document.getElementById(domElId))
406 | #else
407 | failwith "mountById is not available for SSR"
408 | #endif
409 | /// Finds the first DOM element matching a CSS selector and mounts the React element there
410 | /// Important: Not available in SSR
411 | let inline mountBySelector (domElSelector: string) (reactEl: ReactElement): unit =
412 | #if FABLE_COMPILER
413 | ReactDom.render(reactEl, document.querySelector(domElSelector))
414 | #else
415 | failwith "mountBySelector is not available for SSR"
416 | #endif
417 |
--------------------------------------------------------------------------------
/src/Fable.React/Fable.React.Isomorphic.fs:
--------------------------------------------------------------------------------
1 | module Fable.React.Isomorphic
2 |
3 | module Components =
4 | type HybridState =
5 | { isClient: bool }
6 |
7 | type HybridProps<'P> =
8 | { clientView: 'P -> ReactElement
9 | serverView: 'P -> ReactElement
10 | model: 'P }
11 |
12 | type HybridComponent<'P>(initProps) as this =
13 | inherit Component, HybridState>(initProps) with
14 | do this.setInitState { isClient=false }
15 |
16 | override __.componentDidMount() =
17 | this.setState(fun _ _ -> { isClient=true })
18 |
19 | override x.render() =
20 | if x.state.isClient
21 | then x.props.clientView x.props.model
22 | else x.props.serverView x.props.model
23 |
24 | /// Isomorphic helper function for conditional executaion
25 | /// it will execute `clientFn model` on the client side and `serverFn model` on the server side
26 | let inline isomorphicExec (clientFn: 'a -> 'b) (serverFn: 'a -> 'b) (input: 'a) =
27 | #if FABLE_COMPILER
28 | clientFn input
29 | #else
30 | serverFn input
31 | #endif
32 |
33 | let isomorphicView (clientView: 'model -> ReactElement) (serverView: 'model -> ReactElement) (model: 'model) =
34 | #if FABLE_COMPILER
35 | ofType, _, _>
36 | { clientView=clientView; serverView=serverView; model=model } []
37 | #else
38 | serverView model
39 | #endif
40 |
--------------------------------------------------------------------------------
/src/Fable.React/Fable.React.ReactiveComponents.fs:
--------------------------------------------------------------------------------
1 | namespace Fable.React
2 |
3 | /// Helpers for ReactiveComponents (see #44)
4 | module ReactiveComponents =
5 |
6 | type Props<'P, 'S, 'Msg> =
7 | { key: string
8 | props: 'P
9 | update: 'Msg -> 'S -> 'S
10 | view: Model<'P, 'S> -> ('Msg->unit) -> ReactElement
11 | init: 'P -> 'S }
12 |
13 | and State<'T> =
14 | { value: 'T }
15 |
16 | and Model<'P, 'S> =
17 | { props: 'P
18 | state: 'S
19 | children: ReactElement[] }
20 |
21 | type ReactiveCom<'P, 'S, 'Msg>(initProps) =
22 | inherit Component, State<'S>>(initProps)
23 | do base.setInitState { value = initProps.init(initProps.props) }
24 |
25 | override this.render() =
26 | let model =
27 | { props = this.props.props
28 | state = this.state.value
29 | children = this.children }
30 | this.props.view model (fun msg ->
31 | let newState = this.props.update msg this.state.value
32 | this.setState(fun _ _ -> { value = newState }))
33 |
34 | []
35 | module ReactiveComponentsHelpers =
36 | open ReactiveComponents
37 |
38 | /// Renders a stateful React component from functions similar to Elmish
39 | /// * `init` - Initializes component state with given props
40 | /// * `update` - Updates the state when `dispatch` is triggered
41 | /// * `view` - Render function, receives a `ReactiveComponents.Model` object and a `dispatch` function
42 | /// * `key` - The key is necessary to identify React elements in a list, an empty string can be passed otherwise
43 | /// * `props` - External properties passed to the component each time it's rendered, usually from its parent
44 | /// * `children` - A list of children React elements
45 | let reactiveCom<'P, 'S, 'Msg>
46 | (init: 'P -> 'S)
47 | (update: 'Msg -> 'S -> 'S)
48 | (view: Model<'P, 'S> -> ('Msg->unit) -> ReactElement)
49 | (key: string)
50 | (props: 'P)
51 | (children: ReactElement seq): ReactElement =
52 | ofType, Props<'P, 'S, 'Msg>, State<'S>>
53 | { key=key; props=props; update=update; view=view; init=init }
54 | children
55 |
--------------------------------------------------------------------------------
/src/Fable.React/Fable.React.Standard.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Fable.React.Standard
3 |
4 | open Fable.React
5 |
6 | let inline a props children = domEl "a" props children
7 | let inline abbr props children = domEl "abbr" props children
8 | let inline address props children = domEl "address" props children
9 | let inline article props children = domEl "article" props children
10 | let inline aside props children = domEl "aside" props children
11 | let inline audio props children = domEl "audio" props children
12 | let inline b props children = domEl "b" props children
13 | let inline bdi props children = domEl "bdi" props children
14 | let inline bdo props children = domEl "bdo" props children
15 | let inline big props children = domEl "big" props children
16 | let inline blockquote props children = domEl "blockquote" props children
17 | let inline body props children = domEl "body" props children
18 | let inline button props children = domEl "button" props children
19 | let inline canvas props children = domEl "canvas" props children
20 | let inline caption props children = domEl "caption" props children
21 | let inline cite props children = domEl "cite" props children
22 | let inline code props children = domEl "code" props children
23 | let inline colgroup props children = domEl "colgroup" props children
24 | let inline data props children = domEl "data" props children
25 | let inline datalist props children = domEl "datalist" props children
26 | let inline dd props children = domEl "dd" props children
27 | let inline del props children = domEl "del" props children
28 | let inline details props children = domEl "details" props children
29 | let inline dfn props children = domEl "dfn" props children
30 | let inline dialog props children = domEl "dialog" props children
31 | let inline div props children = domEl "div" props children
32 | let inline dl props children = domEl "dl" props children
33 | let inline dt props children = domEl "dt" props children
34 | let inline em props children = domEl "em" props children
35 | let inline fieldset props children = domEl "fieldset" props children
36 | let inline figcaption props children = domEl "figcaption" props children
37 | let inline figure props children = domEl "figure" props children
38 | let inline footer props children = domEl "footer" props children
39 | let inline form props children = domEl "form" props children
40 | let inline h1 props children = domEl "h1" props children
41 | let inline h2 props children = domEl "h2" props children
42 | let inline h3 props children = domEl "h3" props children
43 | let inline h4 props children = domEl "h4" props children
44 | let inline h5 props children = domEl "h5" props children
45 | let inline h6 props children = domEl "h6" props children
46 | let inline head props children = domEl "head" props children
47 | let inline header props children = domEl "header" props children
48 | let inline hgroup props children = domEl "hgroup" props children
49 | let inline html props children = domEl "html" props children
50 | let inline i props children = domEl "i" props children
51 | let inline iframe props children = domEl "iframe" props children
52 | let inline ins props children = domEl "ins" props children
53 | let inline kbd props children = domEl "kbd" props children
54 | let inline label props children = domEl "label" props children
55 | let inline legend props children = domEl "legend" props children
56 | let inline li props children = domEl "li" props children
57 | let inline main props children = domEl "main" props children
58 | let inline map props children = domEl "map" props children
59 | let inline mark props children = domEl "mark" props children
60 | let inline menu props children = domEl "menu" props children
61 | let inline meter props children = domEl "meter" props children
62 | let inline nav props children = domEl "nav" props children
63 | let inline noscript props children = domEl "noscript" props children
64 | let inline object props children = domEl "object" props children
65 | let inline ol props children = domEl "ol" props children
66 | let inline optgroup props children = domEl "optgroup" props children
67 | let inline option props children = domEl "option" props children
68 | let inline output props children = domEl "output" props children
69 | let inline p props children = domEl "p" props children
70 | let inline picture props children = domEl "picture" props children
71 | let inline pre props children = domEl "pre" props children
72 | let inline progress props children = domEl "progress" props children
73 | let inline q props children = domEl "q" props children
74 | let inline rp props children = domEl "rp" props children
75 | let inline rt props children = domEl "rt" props children
76 | let inline ruby props children = domEl "ruby" props children
77 | let inline s props children = domEl "s" props children
78 | let inline samp props children = domEl "samp" props children
79 | let inline script props children = domEl "script" props children
80 | let inline section props children = domEl "section" props children
81 | let inline select props children = domEl "select" props children
82 | let inline small props children = domEl "small" props children
83 | let inline span props children = domEl "span" props children
84 | let inline strong props children = domEl "strong" props children
85 | let inline style props children = domEl "style" props children
86 | let inline sub props children = domEl "sub" props children
87 | let inline summary props children = domEl "summary" props children
88 | let inline sup props children = domEl "sup" props children
89 | let inline table props children = domEl "table" props children
90 | let inline tbody props children = domEl "tbody" props children
91 | let inline td props children = domEl "td" props children
92 | let inline textarea props children = domEl "textarea" props children
93 | let inline tfoot props children = domEl "tfoot" props children
94 | let inline th props children = domEl "th" props children
95 | let inline thead props children = domEl "thead" props children
96 | let inline time props children = domEl "time" props children
97 | let inline title props children = domEl "title" props children
98 | let inline tr props children = domEl "tr" props children
99 | let inline u props children = domEl "u" props children
100 | let inline ul props children = domEl "ul" props children
101 | let inline var props children = domEl "var" props children
102 | let inline video props children = domEl "video" props children
103 |
104 | // Void element
105 | let inline area props = voidEl "area" props
106 | let inline ``base`` props = voidEl "base" props
107 | let inline br props = voidEl "br" props
108 | let inline col props = voidEl "col" props
109 | let inline embed props = voidEl "embed" props
110 | let inline hr props = voidEl "hr" props
111 | let inline img props = voidEl "img" props
112 | let inline input props = voidEl "input" props
113 | let inline keygen props = voidEl "keygen" props
114 | let inline link props = voidEl "link" props
115 | let inline menuitem props = voidEl "menuitem" props
116 | let inline meta props = voidEl "meta" props
117 | let inline param props = voidEl "param" props
118 | let inline source props = voidEl "source" props
119 | let inline track props = voidEl "track" props
120 | let inline wbr props = voidEl "wbr" props
121 |
122 | // SVG elements
123 | let inline svg props children = svgEl "svg" props children
124 | let inline circle props children = svgEl "circle" props children
125 | let inline clipPath props children = svgEl "clipPath" props children
126 | let inline defs props children = svgEl "defs" props children
127 | let inline ellipse props children = svgEl "ellipse" props children
128 | let inline g props children = svgEl "g" props children
129 | let inline image props children = svgEl "image" props children
130 | let inline line props children = svgEl "line" props children
131 | let inline linearGradient props children = svgEl "linearGradient" props children
132 | let inline mask props children = svgEl "mask" props children
133 | let inline path props children = svgEl "path" props children
134 | let inline pattern props children = svgEl "pattern" props children
135 | let inline polygon props children = svgEl "polygon" props children
136 | let inline polyline props children = svgEl "polyline" props children
137 | let inline radialGradient props children = svgEl "radialGradient" props children
138 | let inline rect props children = svgEl "rect" props children
139 | let inline stop props children = svgEl "stop" props children
140 | let inline text props children = svgEl "text" props children
141 | let inline tspan props children = svgEl "tspan" props children
142 |
--------------------------------------------------------------------------------
/src/Fable.React/Fable.React.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 10.0.0
5 | 10.0.0-alpha.1
6 | netstandard2.0
7 |
8 | fsharp;fable;javascript;f#;js;react;fable-library;fable-javascript;fable-dotnet
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/Fable.React/RELEASE_NOTES.md:
--------------------------------------------------------------------------------
1 | ### 10.0.0-alpha.1
2 |
3 | - Upgrade `FSharp.Core` to 9.0.100 to support F# nullness
4 |
5 | ### 9.4.0
6 |
7 | - Fix FunctionComponent's render func caching for generic usages
8 |
9 | ### 9.3.0
10 |
11 | - Add pointer events to `DOMAttr`
12 |
13 | ### 9.2.0
14 |
15 | - Add tags for indexing in Fable packages project
16 |
17 | ### 9.1.0
18 |
19 | - Add Fable.ReactDom.Types dependency
20 |
21 | ### 9.0.1
22 |
23 | - Fix compilation error
24 |
25 | ### 9.0.0
26 |
27 | - Move React types to Fable.React.Types package
28 |
29 | ### 8.0.1
30 |
31 | - Fix hydrateRoot @kerams
32 |
33 | ### 8.0.0
34 |
35 | - Add React 18 new APIs @kerams
36 | - Add Femto metadata @kerams
37 |
38 | ### 7.4.3
39 |
40 | - Fix FunctionComponent.Lazy for SSR @mvsmal
41 |
42 | ### 7.4.2
43 |
44 | - Add DOMAttr.Custom
45 |
46 | ### 7.4.1
47 |
48 | - Add license meta data (by @nojaf)
49 |
50 | ### 7.4.0
51 |
52 | - Lower FSharp.Core requirements
53 |
54 | ### 7.3.0
55 |
56 | - Supersede 7.1/7.2 from PR #208
57 | - Fix #210: defend hmr code
58 |
59 | ### 7.0.3
60 |
61 | - Fix FunctionComponent HMR, see Fable #2414
62 |
63 | ### 7.0.2
64 |
65 | - ReactServer: Optimize cssProps cache @medigor @delneg
66 |
67 | ### 7.0.1
68 |
69 | - Re-release version 7.0.0 because I unlisted it (I forgot to pull the latest changes from the master branch...)
70 |
71 | ### 7.0.0
72 |
73 | - Upgrade Fable.Browser.Dom to 2.0.1 making it easier for package dependant on Fable.React to use the new version of Fable.Browser.Dom
74 |
75 | ### 6.2.0
76 |
77 | - Fix notation for SVG attributes SSR (by @mvsmal)
78 |
79 | ### 6.1.0
80 |
81 | - FunctionComponent: Suspend memoize during HMR
82 |
83 | ### 6.0.0
84 |
85 | - Rework FunctionComponent to make it easier to use, it is also supporting HMR out of the box
86 |
87 | ### 5.4.0
88 |
89 | - Fix notation for SVG attributes SSR (by @mvsmal). The commit has been cherry picked from 6.2.0 release
90 |
91 | ### 5.3.6
92 |
93 | - Add `JustifySelf` used in Grid containers @Luiz-Monad
94 | - Add `StrokeDashoffset` to `SVGAttr`
95 |
96 | ### 5.3.5
97 |
98 | - Complete aria props @Krzysztof-Cieslak
99 |
100 | ### 5.3.4
101 |
102 | - Removed Hyphen from CssProp #183 @SCullman
103 |
104 | ### 5.3.3
105 |
106 | - Fix missing hyphens for some typed css props #181 @SCullman
107 |
108 | ### 5.3.2
109 |
110 | - Fix Hooks.useEffectDisposable closure @dbrattli
111 |
112 | ### 5.3.1
113 |
114 | - SSR: Fix race condition in CSS props cache @forki
115 | - Add CSSProp.UserSelect
116 |
117 | ### 5.3.0
118 |
119 | - Use typed options for remaining CSS props with untyped value @jannesiera
120 | - SSR: Void tags should contain a space @forki
121 | - SSR: Fix StringEnum CSS props
122 | - SSR: Cache CSS props
123 |
124 | ### 5.2.7
125 |
126 | - Add `forwardRef` @Luiz-Monad
127 | - Add `useReducer` @nojaf
128 |
129 | ### 5.2.6
130 |
131 | - Fix #167: Add `withKey` argument to `FunctionComponent.Of`
132 |
133 | ### 5.2.5
134 |
135 | - Fix #166: Expose `mountById`/`mountBySelector` in .NET
136 |
137 | ### 5.2.4
138 |
139 | - Add `contextConsumer`
140 |
141 | ### 5.2.3
142 |
143 | - Fix `Hooks.useStateLazy`
144 |
145 | ### 5.2.2
146 |
147 | - Undeprecate `ofFunction`
148 |
149 | ### 5.2.1
150 |
151 | - Add React Context
152 |
153 | ### 5.1.0
154 |
155 | - Add non-generic `ReactElementType`
156 | - Add memoEqualsButFunctions @vbfox
157 |
158 | ### 5.0.0
159 |
160 | - Function components
161 | - React hooks
162 | - Type-safe CSS props
163 | - Code splitting
164 |
165 | ### 5.0.0-beta-009
166 |
167 | - Add `FunctionComponent`
168 |
169 | ### 5.0.0-beta-007
170 |
171 | - Add `LazyComponent.FromExternalFunction`
172 |
173 | ### 5.0.0-beta-006
174 |
175 | - Add some additional hooks
176 |
177 | ### 5.0.0-beta-004
178 |
179 | - Fix `Hooks.useEffectDisposable` and `equalsButFunctions`
180 |
181 | ### 5.0.0-beta-003
182 |
183 | - Add `equalsButFunctions` @vbfox
184 |
185 | ### 5.0.0-beta-002
186 |
187 | - Fix `object` tag
188 | - Fix effect hook
189 |
190 | ### 5.0.0-beta-001
191 |
192 | - Add type-safe css properties @Zaid-Ajaj
193 |
194 | ### 5.0.0-alpha-005
195 |
196 | - Change dependency to Fable.Browser.Dom
197 | - Rename namespace to Fable.React
198 | - Add Hooks' support
199 |
200 | ### 5.0.0-alpha-004
201 |
202 | - Fix `b`
203 |
204 | ### 5.0.0-alpha-003
205 |
206 | - Fix compilation with fable-splitter and `allFiles` #122 @nojaf
207 | - Add `ValueMultiple` prop for `select` elements with `Multiple` prop #123
208 |
209 | ### 5.0.0-alpha-002
210 |
211 | - Fix typos in doc comment of `memoBuilderWith` and `memoBuilder`
212 |
213 | ### 5.0.0-alpha-001
214 |
215 | - Add bindings for `React.memo`
216 | - Add `nothing` helper
217 |
218 | ### 4.1.1
219 |
220 | - Mark `setState: 'S` as obsolete @SirUppyPancakes
221 | - Enable writing into a TextWritter in SSR @thinkbeforecoding
222 |
223 | ### 4.0.1
224 |
225 | - Update Fable 2 deps to stable version
226 |
227 | ### 4.0.0
228 |
229 | - Release stable version
230 | - Make `Data` type consistent across compiler directive
231 |
232 | ### 4.0.0-alpha-001
233 |
234 | Alpha version for Fable 2
235 |
236 | ### 3.1.2
237 |
238 | - Include documentation in the package
239 | - Add `FromEvent.Value` helper
240 |
241 | ### 3.1.1
242 |
243 | - Remove ReactiveComponents.Model.key as React doesn't allow access to props.key
244 |
245 | ### 3.1.0
246 |
247 | - Speed-up Server-Side rendering @forki
248 | - Make escapeHtml faster @zaaack
249 |
250 | ### 3.0.0
251 |
252 | - Stable version
253 |
254 | ### 3.0.0-alpha-003
255 |
256 | - Give precedence to CSSProps
257 |
258 | ### 3.0.0-alpha-002
259 |
260 | - Add Server Side Rendering Support
261 |
262 | ### 3.0.0-alpha-001
263 |
264 | - Alpha release of next major version
265 |
266 | ### 2.1.0
267 |
268 | - Add React PureComponent and Fragment (@vbfox)
269 |
270 | ### 2.0.0
271 |
272 | - Stable version
273 |
274 | ### 2.0.0-beta-002
275 |
276 | - Add BoxShadow CSSProp (@worldbeater)
277 | - Add Class as alias of ClassName
278 |
279 | ### 2.0.0-beta-001
280 |
281 | - Add `reactiveCom` helper (stateful mini-Elmish component)
282 | - Add `mountById` and `mountBySelectors`
283 | - Add abstract methods to `React.Component` so users have autocompletion and docs when overriding
284 | - Uniform helpers API: ofType, ofFunction, ofImport, ofOption, ofArray...
285 |
286 | ### 1.2.2
287 |
288 | - Add Height to SVGProp
289 |
290 | ### 1.2.1
291 |
292 | - Use new `ParamList` attribute
293 |
294 | ### 1.2.0
295 |
296 | - Release stable version
297 |
298 | ### 1.2.0-beta-003
299 |
300 | - Add CSSProp.OverflowY
301 |
302 | ### 1.2.0-beta-002
303 |
304 | - Target netstandard1.6 again
305 |
306 | ### 1.2.0-beta-001
307 |
308 | - Upgrade to netstandard2.0
309 | - Add react-dom/server methods
310 | - Add Animation and Transition events
311 | - Remove Erased union types from Prop helpers (breaking)
312 | - Give precedence to CSSProps
313 | - Fix SVG elements with mixed props
314 | - Comment Data and uncomment Height HTMLProps
315 | - Add CSSProp.Cursor
316 |
317 | ### 1.1.0
318 |
319 | - Release stable version
320 |
--------------------------------------------------------------------------------
/src/Fable.ReactDom.Types/Fable.ReactDom.Types.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 19.0.0
5 | 19.0.0-alpha.1
6 | netstandard2.0
7 | enable
8 |
9 | fsharp;fable;javascript;f#;js;react;fable-binding;fable-javascript
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/Fable.ReactDom.Types/Fable.ReactDom.fs:
--------------------------------------------------------------------------------
1 | namespace Fable.React
2 |
3 | open Fable.Core
4 | open Fable.React
5 | open Browser.Types
6 |
7 | type IReactRoot =
8 | /// Render a React element into the root.
9 | abstract render: element: ReactElement -> unit
10 |
11 | /// Remove the root from the DOM.
12 | abstract unmount: unit -> unit
13 |
14 | type IRootOptions =
15 | /// Optional callback called when React automatically recovers from errors.
16 | abstract onRecoverableError: (obj -> unit) with get, set
17 |
18 | /// Optional prefix React uses for ids generated by React.useId. Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix used on the server.
19 | abstract identifierPrefix: string with get, set
20 |
21 | type ICreateRootOptions =
22 | inherit IRootOptions
23 |
24 | type IHydrateRootOptions =
25 | inherit IRootOptions
26 |
27 | type IReactDom =
28 | /// Render a React element into the DOM in the supplied container.
29 | /// If the React element was previously rendered into container, this will perform an update on it and only mutate the DOM as necessary to reflect the latest React element.
30 | /// If the optional callback is provided, it will be executed after the component is rendered or updated.
31 | abstract render: element: ReactElement * container: Element * ?callback: (unit->unit) -> unit
32 |
33 | /// Same as render(), but is used to hydrate a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup.
34 | abstract hydrate: element: ReactElement * container: Element * ?callback: (unit->unit) -> unit
35 |
36 | /// Remove a mounted React component from the DOM and clean up its event handlers and state. If no component was mounted in the container, calling this function does nothing. Returns true if a component was unmounted and false if there was no component to unmount.
37 | abstract unmountComponentAtNode: container: Element -> bool
38 |
39 | /// Creates a portal. Portals provide a way to render children into a DOM node that exists outside the hierarchy of the DOM component.
40 | abstract createPortal: child: ReactElement * container: Element -> ReactElement
41 |
42 | /// Force React to flush any updates inside the provided callback synchronously. This ensures that the DOM is updated immediately.
43 | abstract flushSync: callback: (unit -> unit) -> unit
44 |
45 | type IReactDomClient =
46 | /// Create a React root for the supplied container and return the root. The root can be used to render a React element into the DOM with render.
47 | /// Requires React 18.
48 | abstract createRoot: container: Element * ?options: ICreateRootOptions -> IReactRoot
49 |
50 | /// Same as createRoot(), but is used to hydrate a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup.
51 | /// Requires React 18.
52 | abstract hydrateRoot: container: Element * element: ReactElement * ?options: IHydrateRootOptions -> IReactRoot
53 |
54 | type IReactDomServer =
55 | /// Render a React element to its initial HTML. This should only be used on the server. React will return an HTML string. You can use this method to generate HTML on the server and send the markup down on the initial request for faster page loads and to allow search engines to crawl your pages for SEO purposes.
56 | /// If you call ReactDOM.render() on a node that already has this server-rendered markup, React will preserve it and only attach event handlers, allowing you to have a very performant first-load experience.
57 | abstract renderToString: element: ReactElement -> string
58 |
59 | /// Similar to renderToString, except this doesn't create extra DOM attributes such as data-reactid, that React uses internally. This is useful if you want to use React as a simple static page generator, as stripping away the extra attributes can save lots of bytes.
60 | abstract renderToStaticMarkup: element: ReactElement -> string
61 |
62 | []
63 | module ReactDomBindings =
64 | #if FABLE_REPL_LIB
65 | []
66 | #else
67 | []
68 | #endif
69 | let ReactDom: IReactDom = jsNative
70 |
71 | /// Requires React 18.
72 | #if FABLE_REPL_LIB
73 | []
74 | #else
75 | []
76 | #endif
77 | let ReactDomClient: IReactDomClient = jsNative
78 |
79 | #if !FABLE_REPL_LIB
80 | []
81 | let ReactDomServer: IReactDomServer = jsNative
82 | #endif
83 |
--------------------------------------------------------------------------------
/src/Fable.ReactDom.Types/RELEASE_NOTES.md:
--------------------------------------------------------------------------------
1 | ### 19.0.0-alpha.1
2 |
3 | - Upgrade `FSharp.Core` to 9.0.100 to support F# nullness
4 | - Compile will Nullable enabled
5 |
6 | ### 18.2.0
7 |
8 | - Add tags for indexing in Fable packages project
9 |
10 | ### 18.1.0
11 |
12 | - Include source files under `fable` folder
13 |
14 | ### 18.0.0
15 |
16 | - React DOM 18 types
17 |
--------------------------------------------------------------------------------