Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 13 additions & 34 deletions docs/docs/docs/React/apis/lazy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,19 @@ import ReactRefAdmonition from '@site/src/components/ReactRefAdmonition';

`React.lazy` lets you define components that are loaded dynamically, enabling code-splitting and improving performance by only loading components when needed. Lazy components must be rendered inside a `Suspense` boundary to handle loading states.

```fsharp
// Simulate a lazy component
let LazyHello: LazyComponent<unit> =
React.lazy'(fun () ->
promise {
do! Promise.sleep 2000
return! JsInterop.importDynamic "./Counter"
}
)

[<ReactComponent(true)>]
let SuspenseDemo() =
let load, setLoad = React.useState(false)
Html.div [
Html.h3 [ prop.text "Suspense Example" ]
Html.p "Loading the component will take 2 seconds. Then the component will be cached and future reruns will be instant."
if load then
React.Suspense([
React.lazyRender(LazyHello, ())
],
Html.div [ prop.text "Loading..." ]
)
else
Html.button [
prop.text (if load then "Hide Lazy Component" else "Load Lazy Component")
prop.onClick (fun _ -> setLoad(not load))
]
]
```
## `[<ReactLazyComponent>]` attribute

This is the recommended way to define lazy components in Feliz. The attribute automatically wraps the component in `React.lazy` and ensures correct transpilation.

Check out the [ReactLazyComponent documentation](../../feliz/react-component#reactlazycomponent) for more details.

## React.lazy'

`React.lazy'` is a lower-level API that allows you to define lazy components.

To ensure correct rendering:
- use `React.lazy'` to define lazy components and `React.lazyRender` to render them.
- `React.lazy'` must be used as `let`-binding to ensure correct transpilation as `const` in JavaScript.

import SuspenseDemo from '../../../feliz-docs/fableoutput/Examples/React/Suspense'
import RawSuspenseDemo from '!!raw-loader!../../../feliz-docs/Examples/React/Suspense.fs'
Expand All @@ -48,8 +32,3 @@ import RawSuspenseDemo from '!!raw-loader!../../../feliz-docs/Examples/React/Sus
<SuspenseDemo />
</ComponentRender>

## Feliz specifics

To ensure correct rendering of lazy components in Feliz:
- use `React.lazy'` to define lazy components and `React.lazyRender` to render them.
- `React.lazy'` must be used as `let`-binding to ensure correct transpilation as `const` in JavaScript.
83 changes: 68 additions & 15 deletions docs/docs/docs/feliz/react-component.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ sidebar_position: 4
import ComponentRender from '@site/src/components/ComponentRender';
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import CodeBlock from '@theme/CodeBlock';

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.

Expand Down Expand Up @@ -152,37 +153,89 @@ export const Component = memo((componentInputProps) => {

## `[<ReactLazyComponent>]`

:::danger
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`.

This is still work in progress and might change in future releases.
There are two ways to define a lazy component with `[<ReactLazyComponent>]`:

:::info
Remember that only default exports can be lazy loaded!

```fsharp
[<ReactComponent(true)>] // like this!
```
:::

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`.
### From existing component

<Tabs>
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!)

import RawLazy from '!!raw-loader!../../feliz-docs/Examples/Feliz/ReactLazyComponent.fs'
import RawLazyTranspiled from '!!raw-loader!../../feliz-docs/fableoutput/Examples/Feliz/ReactLazyComponent.jsx'
import RawLists from '!!raw-loader!../../feliz-docs/Examples/Feliz/RenderingLists.fs'

<Tabs>
<TabItem value="F#" label="F#">

```fsharp
[<ReactLazyComponent>]
let ComponentLazy () =
Fable.Core.JsInterop.importDynamic "./RenderingLists" // path to file
|> unbox<ReactElement>
```
<CodeBlock language='fsharp'>
{RawLazy}
</CodeBlock>

</TabItem>

<TabItem value="JSX" label="JSX">

```jsx
export const ComponentLazy = lazy(() => {
return import("./RenderingLists");
});
<CodeBlock language='fsharp'>
{RawLazyTranspiled}
</CodeBlock>

```
</TabItem>

<TabItem value="F#-lists" label="F# List Component">

<CodeBlock language='fsharp'>
{RawLists}
</CodeBlock>

</TabItem>

</Tabs>

### From path

Alternatively you can specify a path to a module that contains a default export of a React component.

:::warning
This approach will not have type safety for the component props! It is similiar to writing a JavaScript binding!
:::

import RawLazyPath from '!!raw-loader!../../feliz-docs/Examples/Feliz/ReactLazyComponentPath.fs'
import RawLazyPathTranspiled from '!!raw-loader!../../feliz-docs/fableoutput/Examples/Feliz/ReactLazyComponentPath.jsx'

<Tabs>
<TabItem value="F#" label="F#">

<CodeBlock language='fsharp'>
{RawLazyPath}
</CodeBlock>

</TabItem>

<TabItem value="JSX" label="JSX">

<CodeBlock language='fsharp'>
{RawLazyPathTranspiled}
</CodeBlock>

</TabItem>

<TabItem value="F#-lists" label="F# List Component">

<CodeBlock language='fsharp'>
{RawLists}
</CodeBlock>

</TabItem>

</Tabs>
5 changes: 1 addition & 4 deletions docs/docs/feliz-docs/Examples/Feliz/ReactComponent.fs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module Examples.ReactComponent
module Examples.Feliz.ReactComponent

open Feliz

Expand All @@ -23,6 +23,3 @@ let ComponentMemo (text: string) (count: int) =
]
]


[<ReactLazyComponent>]
let ComponentLazy () = Fable.Core.JsInterop.importDynamic "./RenderingLists" |> unbox<ReactElement>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module Examples.ReactComponentImport
module Examples.Feliz.ReactComponentImport

open Feliz

Expand Down
28 changes: 28 additions & 0 deletions docs/docs/feliz-docs/Examples/Feliz/ReactLazyComponent.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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])
]

28 changes: 28 additions & 0 deletions docs/docs/feliz-docs/Examples/Feliz/ReactLazyComponentPath.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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])
]

6 changes: 3 additions & 3 deletions docs/docs/feliz-docs/Examples/Feliz/RenderingLists.fs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
module Example.RenderingLists
module Examples.Feliz.RenderingLists

open Feliz

type RenderingLists =

[<ReactComponent(true)>]
static member Example() =
static member Example(?list: int list) =

let items = [ 0 .. 5 ] //any list/seq/array
let items = defaultArg list [ 0 .. 5 ] //any list/seq/array

Html.div [
Html.h2 "List rendering using for loop"
Expand Down
4 changes: 3 additions & 1 deletion docs/docs/feliz-docs/Examples/React/Suspense.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ module Example.Suspense
open Feliz
open Fable.Core

// Simulate a lazy component
/// Lazy load with delay to simulate large component
///
/// Note: Prefer using `[<ReactLazyComponent>]` instead of this approach!
let LazyHello: LazyComponent<unit> =
React.lazy'(fun () ->
promise {
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/feliz-docs/Examples/React/UseCallback.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ open Feliz


[<ReactMemoComponent>] // memoizes component to prevent rerender whenever parent rerenders
let ChildComponent (onClick: unit -> unit) =
let ChildComponent(onClick: unit -> unit) =
let renderCount = React.useRef(0)
React.useEffect(fun () ->
renderCount.current <- renderCount.current + 1
Expand Down
2 changes: 2 additions & 0 deletions docs/docs/feliz-docs/Feliz.Docs.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
<Compile Include="Examples/Feliz/RenderingLists.fs" />
<Compile Include="Examples/Feliz/ReactComponent.fs" />
<Compile Include="Examples/Feliz/ReactComponentImport.fs" />
<Compile Include="Examples/Feliz/ReactLazyComponent.fs" />
<Compile Include="Examples/Feliz/ReactLazyComponentPath.fs" />
<Compile Include="Examples/Guides/UnboxStringEnum.fs" />
<Compile Include="Examples/Guides/BrowserTypesEvents.fs" />
<Compile Include="Examples/Guides/BrowserTypesGlobalNamespaces.fs" />
Expand Down
2 changes: 1 addition & 1 deletion playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "dotnet fable watch ./src -o ./src/fableoutput --exclude Feliz.CompilerPlugins -e .tsx --lang ts --noCache --run vite",
"dev": "dotnet fable watch ./src -o ./src/fableoutput --exclude Feliz.CompilerPlugins -e .tsx --lang ts --noCache --run vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
Expand Down
2 changes: 1 addition & 1 deletion playground/src/App.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ open Fable.Core
let App() =
Html.div [
prop.children [
Components.Components.Counter()
Components.Main()
]
]
1 change: 1 addition & 0 deletions playground/src/App.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="REALLYNEEDTOLOOKFORME.fs" />
<Compile Include="Components.fs" />
<Compile Include="App.fs" />
<Compile Include="Main.fs" />
Expand Down
Loading