[<ReactComponent>]
Feliz offers a simple way to define React components using the [<ReactComponent>]
attribute. This attribute can be applied to a function that returns a React element.
With the help of [<ReactComponent>]
Feliz can ensure that the components are correctly transformend and called after transpilation to JavaScript.
If you are interested in what happens under the hood, here is a short summary of notable points:
Click to expand
-
Transform all arguments into an JavaScript object.
Tupled/curried arguments are transformed into object and exactly one argument as (anonymous) record type will be used as object.
- Tupled Args
- Curried Args
- Record Type
- Anonymous Record Type
- JSX
[<ReactComponent>]
let Component(text: string, count: int) = ...[<ReactComponent>]
let Component (text: string) (count: int) = ...type Props = { text: string; count: int } // record type
[<ReactComponent>]
let Component(props: Props) = ...type Props = {| text: string; count: int |} // anonymous record type
[<ReactComponent>]
let Component(props: Props) = ...function Component({text, count}) { ... }
-
Component will be called using the
createElement
function from React. -
Import
React
automatically.
export default
You can pass a boolean parameter to the attribute to export the component as the default export of the module.
[<ReactComponent(exportDefault = true)>]
let Component() = ...
// or
[<ReactComponent(true)>]
let Component() = ...
import .. from
You can pass string arguments to the attribute to import specific named imports from a module.
Counter: 42
Counter: 10
Show code
- NativeCounter.tsx
- ReactComponentImport.fs
import React from "react";
interface Props {
init?: number;
}
export function Counter({init = 42}: Props) {
const [count, setCount] = React.useState(init);
return (
<div>
<h1>Counter: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
}
module Examples.Feliz.ReactComponentImport
open Feliz
type Component =
[<ReactComponent("Counter", "./NativeCounter")>]
static member Counter(?init: int) = React.Imported()
[<ReactComponent(true)>]
static member Example() =
Html.div [
Component.Counter()
Component.Counter(10)
]
[<ReactMemoComponent>]
React memo
components can be created using the [<ReactMemoComponent>]
attribute. It works the same way as [<ReactComponent>]
, but wraps the component in React.memo
and ensures that it is defined as a const
in the generated JavaScript code.
- F#
- JSX
[<ReactMemoComponent>]
let Component(text: string, count: int) =
Html.div [
for i in 1 .. count do
Html.p [
prop.key i
prop.text (sprintf "%d: %s" i text)
]
]
export const Component = memo((componentInputProps) => {
const count = componentInputProps.count;
const text = componentInputProps.text;
// ...
});
[<ReactLazyComponent>]
React lazy
components can be created using the [<ReactLazyComponent>]
attribute. It works similarly to [<ReactComponent>]
, but is intended for components that are loaded dynamically using React.lazy
.
There are two ways to define a lazy component with [<ReactLazyComponent>]
:
Remember that only default exports can be lazy loaded!
[<ReactComponent(true)>] // like this!
From existing component
This approach allows to wrap an existing component in a lazy-loaded component.
If you have optional parameters, you will need to add the arguments you want to use to the wrapper. Otherwise the F# compiler will assume a None for all. (See examples below!)
- F#
- JSX
- F# List Component
module Examples.Feliz.ReactLazyComponent
open Feliz
[<ReactLazyComponent>]
let private LazyLists(list: int list option) = Examples.Feliz.RenderingLists.RenderingLists.Example(?list = list)
[<ReactLazyComponent>]
let private LazyListsNoArg = Examples.Feliz.RenderingLists.RenderingLists.Example
[<Fable.Core.Erase; Fable.Core.Mangle(false)>]
type Examples =
[<ReactLazyComponent>]
static member LazyList(?list: int list) = Examples.Feliz.RenderingLists.RenderingLists.Example(?list = list)
[<ReactComponent(true)>]
static member Main() =
Html.div [
Html.h1 "ReactLazyComponent Example"
Html.h2 "With argument"
LazyLists(Some [1;2;3;4;5])
Html.h2 "Without argument"
LazyListsNoArg()
Html.h2 "Using static member"
Examples.LazyList([10;20;30])
]
import { createElement, lazy } from "react";
import React from "react";
import { defaultOf } from "../../fable_modules/fable-library-js.5.0.0-alpha.14/Util.js";
import { ofArray } from "../../fable_modules/fable-library-js.5.0.0-alpha.14/List.js";
export const LazyLists = lazy(() => {
return import("./RenderingLists.jsx");
});
export const LazyListsNoArg = lazy(() => {
return import("./RenderingLists.jsx");
});
export const LazyList = lazy(() => {
return import("./RenderingLists.jsx");
});
export function Main() {
const children_8 = ofArray([createElement("h1", defaultOf(), "ReactLazyComponent Example"), createElement("h2", defaultOf(), "With argument"), (LazyLists.displayName = "LazyLists", createElement(LazyLists, {
list: ofArray([1, 2, 3, 4, 5]),
})), createElement("h2", defaultOf(), "Without argument"), createElement(LazyListsNoArg, null), createElement("h2", defaultOf(), "Using static member"), (LazyList.displayName = "LazyList", createElement(LazyList, {
list: ofArray([10, 20, 30]),
}))]);
return createElement("div", defaultOf(), ...children_8);
}
export default Main;
module Examples.Feliz.RenderingLists
open Feliz
type RenderingLists =
[<ReactComponent(true)>]
static member Example(?list: int list) =
let items = defaultArg list [ 0 .. 5 ] //any list/seq/array
Html.div [
Html.h2 "List rendering using for loop"
Html.ul [
Html.li "Static item 1"
for item in items do // f# for loop, nicely combinable with other children
Html.li [
prop.key item
prop.text (sprintf "Item %i" item)
]
]
Html.h2 "List rendering using List.map"
items // f# pipe style
|> List.map (fun item ->
Html.li [
prop.key item
prop.text (sprintf "Item %i" item)
])
|> Html.ol
]
From path
Alternatively you can specify a path to a module that contains a default export of a React component.
This approach will not have type safety for the component props! It is similiar to writing a JavaScript binding!
- F#
- JSX
- F# List Component
module Examples.Feliz.ReactLazyComponentPath
open Feliz
[<ReactLazyComponent>]
let private LazyLists(list: int list option) = React.DynamicImported "./RenderingLists"
[<ReactLazyComponent>]
let private LazyListsNoArg() = React.DynamicImported "./RenderingLists"
[<Fable.Core.Erase; Fable.Core.Mangle(false)>]
type Examples =
[<ReactLazyComponent>]
static member LazyList(?list: int list) = React.DynamicImported "./RenderingLists"
[<ReactComponent(true)>]
static member Main() =
Html.div [
Html.h1 "ReactLazyComponent Example"
Html.h2 "With argument"
LazyLists(Some [1;2;3;4;5])
Html.h2 "Without argument"
LazyListsNoArg()
Html.h2 "Using static member"
Examples.LazyList([10;20;30])
]
import { createElement, lazy } from "react";
import React from "react";
import { defaultOf } from "../../fable_modules/fable-library-js.5.0.0-alpha.14/Util.js";
import { ofArray } from "../../fable_modules/fable-library-js.5.0.0-alpha.14/List.js";
export const LazyLists = lazy(() => {
return import("./RenderingLists");
});
export const LazyListsNoArg = lazy(() => {
return import("./RenderingLists");
});
export const LazyList = lazy(() => {
return import("./RenderingLists");
});
export function Main() {
const children_8 = ofArray([createElement("h1", defaultOf(), "ReactLazyComponent Example"), createElement("h2", defaultOf(), "With argument"), (LazyLists.displayName = "LazyLists", createElement(LazyLists, {
list: ofArray([1, 2, 3, 4, 5]),
})), createElement("h2", defaultOf(), "Without argument"), createElement(LazyListsNoArg, null), createElement("h2", defaultOf(), "Using static member"), (LazyList.displayName = "LazyList", createElement(LazyList, {
list: ofArray([10, 20, 30]),
}))]);
return createElement("div", defaultOf(), ...children_8);
}
export default Main;
module Examples.Feliz.RenderingLists
open Feliz
type RenderingLists =
[<ReactComponent(true)>]
static member Example(?list: int list) =
let items = defaultArg list [ 0 .. 5 ] //any list/seq/array
Html.div [
Html.h2 "List rendering using for loop"
Html.ul [
Html.li "Static item 1"
for item in items do // f# for loop, nicely combinable with other children
Html.li [
prop.key item
prop.text (sprintf "Item %i" item)
]
]
Html.h2 "List rendering using List.map"
items // f# pipe style
|> List.map (fun item ->
Html.li [
prop.key item
prop.text (sprintf "Item %i" item)
])
|> Html.ol
]