2019-04-26 10:30:02 +00:00
|
|
|
# React Features
|
|
|
|
|
|
|
|
Most React features should be usable from Reagent, even if Reagent doesn't
|
|
|
|
provide functions to use them directly.
|
|
|
|
|
2019-05-23 18:00:24 +00:00
|
|
|
## [Fragments](https://reactjs.org/docs/fragments.html)
|
|
|
|
|
|
|
|
JSX:
|
|
|
|
```js
|
2019-05-23 18:01:05 +00:00
|
|
|
function example() {
|
2019-05-23 18:00:24 +00:00
|
|
|
return (
|
|
|
|
<React.Fragment>
|
|
|
|
<ChildA />
|
|
|
|
<ChildB />
|
|
|
|
<ChildC />
|
|
|
|
</React.Fragment>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Reagent:
|
|
|
|
```cljs
|
|
|
|
(defn example []
|
|
|
|
[:<>
|
|
|
|
[child-a]
|
|
|
|
[child-b]
|
|
|
|
[child-c]])
|
|
|
|
```
|
|
|
|
|
2019-05-23 18:00:35 +00:00
|
|
|
Reagent syntax follows [React Fragment short syntax](https://reactjs.org/docs/fragments.html#short-syntax).
|
2019-05-23 18:00:24 +00:00
|
|
|
|
2019-05-23 18:05:31 +00:00
|
|
|
## [Context](https://reactjs.org/docs/context.html)
|
2019-04-26 10:30:02 +00:00
|
|
|
|
|
|
|
```cljs
|
|
|
|
(defonce my-context (react/createContext "default"))
|
|
|
|
|
|
|
|
(def Provider (.-Provider my-context))
|
|
|
|
(def Consumer (.-Consumer my-context))
|
|
|
|
|
2019-12-17 20:50:14 +00:00
|
|
|
(rdom/render
|
|
|
|
[:> Provider {:value "bar"}
|
|
|
|
[:> Consumer {}
|
|
|
|
(fn [v]
|
|
|
|
(r/as-element [:div "Context: " v]))]]
|
|
|
|
container)
|
2019-04-26 10:30:02 +00:00
|
|
|
```
|
|
|
|
|
2020-03-28 12:07:59 +00:00
|
|
|
[Context example project](../examples/react-context/src/example/core.cljs)
|
2020-03-28 12:07:24 +00:00
|
|
|
better explains how
|
|
|
|
`:>` or `adapt-react-class` convert the properties to JS objects,
|
|
|
|
and shows how to use Cljs values with context.
|
|
|
|
|
2019-12-12 16:58:59 +00:00
|
|
|
Alternatively you can use the [static contextType property](https://reactjs.org/docs/context.html#classcontexttype)
|
|
|
|
|
|
|
|
```cljs
|
|
|
|
(defonce my-context (react/createContext "default"))
|
|
|
|
|
|
|
|
(def Provider (.-Provider my-context))
|
|
|
|
|
|
|
|
(defn show-context []
|
|
|
|
(r/create-class
|
|
|
|
{:context-type my-context
|
|
|
|
:reagent-render (fn []
|
|
|
|
[:p (.-context (reagent.core/current-component))])}))
|
|
|
|
|
|
|
|
;; Alternatively with metadata on a form-1 component:
|
|
|
|
;;
|
|
|
|
;; (def show-context
|
|
|
|
;; ^{:context-type my-context}
|
|
|
|
;; (fn []
|
|
|
|
;; [:p (.-context (reagent.core/current-component))]))
|
|
|
|
|
2019-12-17 20:50:14 +00:00
|
|
|
(rdom/render
|
|
|
|
[:> Provider {:value "bar"}
|
|
|
|
[show-context]]
|
|
|
|
container)
|
2019-12-12 16:58:59 +00:00
|
|
|
```
|
|
|
|
|
2019-04-26 10:30:02 +00:00
|
|
|
Tests contain example of using old React lifecycle Context API (`context-wrapper` function):
|
2019-12-17 00:21:05 +00:00
|
|
|
[tests](https://github.com/reagent-project/reagent/blob/master/test/reagenttest/testreagent.cljs#L1159-L1168)
|
2019-04-26 10:30:02 +00:00
|
|
|
|
2019-05-23 18:05:31 +00:00
|
|
|
## [Error boundaries](https://reactjs.org/docs/error-boundaries.html)
|
2019-04-26 10:30:02 +00:00
|
|
|
|
2019-10-02 09:59:00 +00:00
|
|
|
[Relevant method docs](https://reactjs.org/docs/react-component.html#static-getderivedstatefromerror)
|
|
|
|
|
2019-10-02 12:37:21 +00:00
|
|
|
You can use `getDerivedStateFromError` (since React 16.6.0 and Reagent 0.9) (and `ComponentDidCatch`) lifecycle method with `create-class`:
|
2019-10-02 09:59:00 +00:00
|
|
|
|
|
|
|
```cljs
|
|
|
|
(defn error-boundary [comp]
|
|
|
|
(let [error (r/atom nil)]
|
|
|
|
(r/create-class
|
2019-10-02 12:37:21 +00:00
|
|
|
{:component-did-catch (fn [this e info])
|
2019-10-09 08:06:54 +00:00
|
|
|
:get-derived-state-from-error (fn [e]
|
2019-10-02 12:37:21 +00:00
|
|
|
(reset! error e)
|
|
|
|
#js {})
|
2019-10-02 09:59:00 +00:00
|
|
|
:reagent-render (fn [comp]
|
|
|
|
(if @error
|
|
|
|
[:div
|
|
|
|
"Something went wrong."
|
|
|
|
[:button {:on-click #(reset! error nil)} "Try again"]]
|
|
|
|
comp))})))
|
|
|
|
```
|
|
|
|
|
|
|
|
Alternatively, one could use React state instead of RAtom to keep track of error state, which
|
|
|
|
can be more obvious with the new `getDerivedStateFromError` method:
|
2019-04-26 10:30:02 +00:00
|
|
|
|
|
|
|
```cljs
|
|
|
|
(defn error-boundary [comp]
|
|
|
|
(r/create-class
|
2019-10-02 09:59:00 +00:00
|
|
|
{:constructor (fn [this props]
|
|
|
|
(set! (.-state this) #js {:error nil}))
|
2019-10-02 12:37:21 +00:00
|
|
|
:component-did-catch (fn [this e info])
|
2019-10-09 08:06:54 +00:00
|
|
|
:get-derived-state-from-error (fn [error] #js {:error error})
|
2019-10-02 09:59:00 +00:00
|
|
|
:render (fn [this]
|
|
|
|
(r/as-element
|
|
|
|
(if @error
|
|
|
|
[:div
|
|
|
|
"Something went wrong."
|
|
|
|
[:button {:on-click #(.setState this #js {:error nil})} "Try again"]]
|
2019-10-02 12:37:21 +00:00
|
|
|
(into [:<>] (r/children this)))})))
|
2019-04-26 10:30:02 +00:00
|
|
|
```
|
|
|
|
|
2019-10-02 12:37:21 +00:00
|
|
|
As per React docs, `getDerivedStateFromError` is what should update the state
|
|
|
|
after error, it can be also used to update RAtom as in Reagent the Ratom is available
|
|
|
|
in function closure even for static methods. `ComponentDidCatch` can be used
|
|
|
|
for side-effects, like logging the error.
|
|
|
|
|
2019-05-23 18:05:31 +00:00
|
|
|
## [Hooks](https://reactjs.org/docs/hooks-intro.html)
|
2019-04-26 10:30:02 +00:00
|
|
|
|
|
|
|
Hooks can't be used inside class components, and Reagent implementation creates
|
|
|
|
a class component from every function (i.e. Reagent component).
|
|
|
|
|
|
|
|
However, you can use React components using Hooks inside Reagent, or use
|
|
|
|
[hx](https://github.com/Lokeh/hx) components inside Reagent. Also, it is
|
|
|
|
possible to create React components from Reagent quite easily, because React
|
|
|
|
function component is just a function that happens to return React elements,
|
|
|
|
and `r/as-element` does just that:
|
|
|
|
|
|
|
|
```cljs
|
|
|
|
;; This is React function component. Can't use Ratoms here!
|
|
|
|
(defn example []
|
|
|
|
(let [[count set-count] (react/useState 0)]
|
|
|
|
(r/as-element
|
|
|
|
[:div
|
|
|
|
[:p "You clicked " count " times"]
|
|
|
|
[:button
|
|
|
|
{:on-click #(set-count inc)}
|
|
|
|
"Click"]])))
|
|
|
|
|
|
|
|
;; Reagent component
|
|
|
|
(defn reagent-component []
|
|
|
|
[:div
|
|
|
|
;; Note :> to use a function as React component
|
|
|
|
[:> example]])
|
|
|
|
```
|
|
|
|
|
|
|
|
If you need to pass RAtom state into these components, dereference them in
|
|
|
|
the Reagent components and pass the value (and if needed, function to update them)
|
|
|
|
as properties into the React function component.
|
|
|
|
|
2019-05-23 18:05:31 +00:00
|
|
|
## [Portals](https://reactjs.org/docs/portals.html)
|
2019-04-26 10:30:02 +00:00
|
|
|
|
|
|
|
```cljs
|
|
|
|
(defn reagent-component []
|
|
|
|
(r/create-class
|
|
|
|
{:render (fn [this]
|
|
|
|
(let [el (.. js/document (getElementById "portal-el"))]
|
|
|
|
(react-dom/createPortal (r/as-element [:div "foo"]) el)))}))
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
TODO: Can this be done without create-class and `:render`.
|
|
|
|
TODO: This might have problems handling Ratoms, test.
|
|
|
|
|
2019-05-23 18:05:31 +00:00
|
|
|
## [Hydrate](https://reactjs.org/docs/react-dom.html#hydrate)
|
2019-04-26 10:30:02 +00:00
|
|
|
|
|
|
|
```cljs
|
|
|
|
(react-dom/hydrate (r/as-element [main-component]) container)
|
|
|
|
```
|