|
| 1 | +--- |
| 2 | +title: Upgrade from 2.x to 3.x |
| 3 | +displayed_sidebar: docsSidebar |
| 4 | +sidebar_position: 999 |
| 5 | +--- |
| 6 | + |
| 7 | +A lot fo F# wrapper magic was removed. React bindings now behave as close as possible to actual React functionality. |
| 8 | + |
| 9 | +## Update Fable Version |
| 10 | + |
| 11 | +Get the latest fable version (currently pre-release). |
| 12 | + |
| 13 | +```bash |
| 14 | +# cmd |
| 15 | +dotnet tool update fable --prerelease |
| 16 | +``` |
| 17 | + |
| 18 | +## Update .NET Framework |
| 19 | + |
| 20 | +Recommended is the use of .NET 8. |
| 21 | + |
| 22 | +```bash |
| 23 | +# cmd |
| 24 | +dotnet --version |
| 25 | +``` |
| 26 | + |
| 27 | +## React.memo |
| 28 | + |
| 29 | +React.memo is used to meoize the rendering of your components and prevent unnecessary rerenders. The recommended usecase is the attribute [<ReactMemoComponent>] but you can also use [React.memo](https://fable-hub.github.io/Feliz/next/api-docs/react/apis/memo) to define a component for memo. When doing so, the component of React.lazy' must be bind with let and be called with React.lazyRender to render it. |
| 30 | + |
| 31 | +```fsharp |
| 32 | +open Feliz |
| 33 | +open Browser.Dom |
| 34 | +
|
| 35 | +[<ReactComponent>] |
| 36 | +let RenderTextWithEffect (text: string) = |
| 37 | + React.useEffect (fun () -> console.log("Rerender!", text) ) |
| 38 | + Html.div [ |
| 39 | + prop.text text; |
| 40 | + prop.testId "memo-attribute" |
| 41 | + ] |
| 42 | +
|
| 43 | +let MemoFunction = |
| 44 | + React.memo<{|text: string|}> (fun props -> |
| 45 | + RenderTextWithEffect(props.text) |
| 46 | + ) |
| 47 | +
|
| 48 | +[<ReactComponent(true)>] |
| 49 | +let Main () = |
| 50 | + let isDark, setIsDark = React.useState(false) |
| 51 | + let text, setText = React.useState("Hello, world!") |
| 52 | + let fgColor = if isDark then color.white else color.black |
| 53 | + let bgColor = if isDark then color.black else color.white |
| 54 | + Html.div [ |
| 55 | + prop.style [style.border(1, borderStyle.solid, fgColor); style.padding 20; style.color fgColor; style.backgroundColor bgColor] |
| 56 | + prop.children [ |
| 57 | + Html.h3 "Check the output in the browser console" |
| 58 | + Html.button [ |
| 59 | + prop.text "Toggle Dark Mode" |
| 60 | + prop.onClick (fun _ -> setIsDark(not isDark)) |
| 61 | + ] |
| 62 | + Html.input [ |
| 63 | + prop.value text |
| 64 | + prop.onChange setText |
| 65 | + ] |
| 66 | + React.memoRender(MemoFunction, {| text = text |}) |
| 67 | + ] |
| 68 | + ] |
| 69 | +``` |
| 70 | + |
| 71 | +## React.lazy' |
| 72 | + |
| 73 | +React.lazy' is used to call components dynamically, also only when needed, in order to reduce the required performance. The recommended usecase is the attribute [<ReactLazyComponent>] but you can also define a lazy loaded comonent using [React.lazy'](https://fable-hub.github.io/Feliz/next/api-docs/react/apis/lazy). When doing so, the component of React.lazy' must be bind with let and be called with React.lazyRender to render it. |
| 74 | + |
| 75 | +```fsharp |
| 76 | +open Feliz |
| 77 | +open Fable.Core |
| 78 | +
|
| 79 | +/// Lazy load with delay to simulate large component |
| 80 | +/// |
| 81 | +/// Note: Prefer using `[<ReactLazyComponent>]` instead of this approach! |
| 82 | +let LazyHello: LazyComponent<unit> = |
| 83 | + React.lazy'(fun () -> |
| 84 | + promise { |
| 85 | + do! Promise.sleep 2000 |
| 86 | + return! JsInterop.importDynamic "./Counter" |
| 87 | + } |
| 88 | + ) |
| 89 | +
|
| 90 | +[<ReactComponent(true)>] |
| 91 | +let SuspenseDemo() = |
| 92 | + let load, setLoad = React.useState(false) |
| 93 | + Html.div [ |
| 94 | + Html.h3 [ prop.text "Suspense Example" ] |
| 95 | + Html.p "Loading the component will take 2 seconds. Then the component will be cached and future reruns will be instant." |
| 96 | + if load then |
| 97 | + React.Suspense([ |
| 98 | + React.lazyRender(LazyHello, ()) |
| 99 | + ], |
| 100 | + Html.div [ prop.text "Loading..." ] |
| 101 | + ) |
| 102 | + else |
| 103 | + Html.button [ |
| 104 | + prop.text (if load then "Hide Lazy Component" else "Load Lazy Component") |
| 105 | + prop.onClick (fun _ -> setLoad(not load)) |
| 106 | + ] |
| 107 | + ] |
| 108 | +``` |
| 109 | + |
| 110 | +## React.context |
| 111 | + |
| 112 | +React.createContext enables the user to create a context for a component in react. That way, values are shared automatically between a component and all its children, without inserting them. In order to use [React.createContext](https://fable-hub.github.io/Feliz/next/api-docs/react/apis/createContext), you must define a reactcontext with a let binding. Then you can call that context in a provider, which inserts the values to be shared in the defined context.Provider and the child components. |
| 113 | + |
| 114 | +```fsharp |
| 115 | +open Feliz |
| 116 | +open Browser.Dom |
| 117 | +
|
| 118 | +open Feliz |
| 119 | +
|
| 120 | +// Define a context for shared state |
| 121 | +// This can should be placed in a separate file for reuse |
| 122 | +let CounterContext = React.createContext(None: (int * (int -> unit)) option) |
| 123 | +
|
| 124 | +[<ReactComponent>] |
| 125 | +let CounterProvider(children: ReactElement list) = |
| 126 | + let count, setCount = React.useState(0) |
| 127 | + CounterContext.Provider(Some(count, setCount), children) |
| 128 | +
|
| 129 | +[<ReactComponent>] |
| 130 | +let CounterDisplay() = |
| 131 | + let ctx = React.useContext(CounterContext) |
| 132 | + match ctx with |
| 133 | + | Some(count, _) -> Html.p [ prop.text $"Current count: {count}" ] |
| 134 | + | None -> Html.p [ prop.text "No context available" ] |
| 135 | +
|
| 136 | +[<ReactComponent>] |
| 137 | +let CounterControls() = |
| 138 | + let ctx = React.useContext(CounterContext) |
| 139 | + match ctx with |
| 140 | + | Some(count, setCount) -> |
| 141 | + Html.div [ |
| 142 | + Html.button [ |
| 143 | + prop.text "+" |
| 144 | + prop.onClick (fun _ -> setCount(count + 1)) |
| 145 | + ] |
| 146 | + Html.button [ |
| 147 | + prop.text "-" |
| 148 | + prop.onClick (fun _ -> setCount(count - 1)) |
| 149 | + ] |
| 150 | + ] |
| 151 | + | None -> Html.p [ prop.text "No context available" ] |
| 152 | +
|
| 153 | +[<ReactComponent(true)>] |
| 154 | +let UseContext() = |
| 155 | + CounterProvider [ |
| 156 | + Html.h3 [ prop.text "Shared Counter" ] |
| 157 | + CounterDisplay() |
| 158 | + CounterControls() |
| 159 | + ] |
| 160 | +
|
| 161 | +``` |
| 162 | + |
| 163 | +## FsReact |
| 164 | + |
| 165 | +All f# functions to help with react interop have been moved to FsReact namespace. |
| 166 | + |
| 167 | +``` |
| 168 | +FsReact.createDisposable |
| 169 | +FsReact.useDisposable |
| 170 | +FsReact.useCancellationToken |
| 171 | +``` |
| 172 | + |
| 173 | +## Components use PascalCase |
| 174 | + |
| 175 | +According to react best practices, components are written in PascalCase instead of camelCase. This has been updated for React. |
| 176 | + |
| 177 | +`React.Fragment, React.KeyedFragment, React.Imported, React.DynamicImported, React.StrictMode, React.Suspense, React.Provider, React.Consumer` |
0 commit comments