mirror of https://github.com/status-im/reagent.git
Merge branch 'master' of github.com:reagent-project/reagent into extends-component
This commit is contained in:
commit
7b4d5ff9c1
|
@ -1,5 +1,11 @@
|
|||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
**[compare](https://github.com/reagent-project/reagent/compare/v0.8.1...master)**
|
||||
|
||||
- Fix using metadata to set React key with Fragment shortcut (`:<>`) ([#401](https://github.com/reagent-project/reagent/issues/401))
|
||||
|
||||
## 0.8.1 (2018-05-15)
|
||||
|
||||
**[compare](https://github.com/reagent-project/reagent/compare/v0.8.0...v0.8.1)**
|
||||
|
|
19
README.md
19
README.md
|
@ -1,6 +1,8 @@
|
|||
|
||||
# Reagent
|
||||
|
||||
![Reagent-Project](logo.png)
|
||||
|
||||
A simple [ClojureScript](http://github.com/clojure/clojurescript) interface to [React](http://facebook.github.io/react/).
|
||||
|
||||
Reagent provides a way to write efficient React components using (almost) nothing but plain ClojureScript functions.
|
||||
|
@ -12,6 +14,11 @@ Reagent provides a way to write efficient React components using (almost) nothin
|
|||
* **Community discussion and support channels**
|
||||
* **[#reagent](https://clojurians.slack.com/messages/reagent/)** channel in [Clojure Slack](http://clojurians.net/)
|
||||
* **[Reagent Project Mailing List](https://groups.google.com/forum/#!forum/reagent-project)**
|
||||
* **Commercial video material**
|
||||
* [Learn Reagent Free](https://www.jacekschae.com/learn-reagent-free/tycit?coupon=REAGENT)
|
||||
* [Learn Reagent Pro](https://www.jacekschae.com/learn-reagent-pro/tycit?coupon=REAGENT) (Affiliate link, $30 discount)
|
||||
* [purelyfunctional.tv ](https://purelyfunctional.tv/guide/reagent/)
|
||||
* [Lambda Island Videos](https://lambdaisland.com/collections/react-reagent-re-frame)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
|
@ -28,7 +35,7 @@ If you wish to only create the assets for ClojureScript without a Clojure backen
|
|||
|
||||
lein new reagent-frontend myproject
|
||||
|
||||
This will setup a new Reagent project with some reasonable defaults, see here for more [details](https://github.com/reagent-project/reagent-template).
|
||||
This will setup a new Reagent project with some reasonable defaults, see here for more [details](https://github.com/reagent-project/reagent-template).
|
||||
|
||||
To use Reagent in an existing project you add this to your dependencies in `project.clj`:
|
||||
|
||||
|
@ -51,7 +58,7 @@ Reagent uses [Hiccup-like](https://github.com/weavejester/hiccup) markup instead
|
|||
(defn some-component []
|
||||
[:div
|
||||
[:h3 "I am a component!"]
|
||||
[:p.someclass
|
||||
[:p.someclass
|
||||
"I have " [:strong "bold"]
|
||||
[:span {:style {:color "red"}} " and red"]
|
||||
" text."]])
|
||||
|
@ -64,15 +71,15 @@ Reagent extends standard Hiccup in one way: it is possible to "squeeze" elements
|
|||
[:p
|
||||
[:b "Nested Element"]]]
|
||||
```
|
||||
|
||||
|
||||
can be written as:
|
||||
|
||||
|
||||
```clj
|
||||
[:div>p>b "Nested Element"]
|
||||
```
|
||||
```
|
||||
|
||||
> **Since version 0.8:** The `:class` attribute also supports collections of classes, and nil values are removed:
|
||||
>
|
||||
>
|
||||
> ```clj
|
||||
> [:div {:class ["a-class" (when active? "active") "b-class"]}]
|
||||
> ```
|
||||
|
|
|
@ -128,7 +128,7 @@
|
|||
|
||||
[:p "The goal of Reagent is to make it possible to define
|
||||
arbitrarily complex UIs using just a couple of basic concepts,
|
||||
and to be fast enough by default that you rarely have to care
|
||||
and to be fast enough by default that you rarely have to think
|
||||
about performance."]
|
||||
|
||||
[:p "A very basic Reagent component may look something like this: "]
|
||||
|
|
|
@ -7,19 +7,8 @@ how you want to provide React.
|
|||
|---|---|---|---|
|
||||
| Cljsjs | `:none` | Supported | Requires Cljs 1.10.238+ |
|
||||
| Cljsjs | `:advanced` | Supported | Requires Cljs 1.10.238+ |
|
||||
| `node modules` | `:none` | Known problems (1) | Supported |
|
||||
| `node modules` | `:advanced` | Known problems (2) | Supported |
|
||||
|
||||
While Reagent 0.8 supports use with React from npm, there are known problems:
|
||||
|
||||
1. Closure can't properly handle React 16 CommonJS module pattern: https://github.com/google/closure-compiler/issues/2841
|
||||
This causes the production React code being loaded even for development builds.
|
||||
Using Chrome React Developer Tools with this setup will break Reagent.
|
||||
|
||||
2. Closure optimization currently breaks certain statically created objects which are
|
||||
accessed dynamically in `ReactDOM/server`: https://github.com/facebook/react/issues/12368
|
||||
Fixed by using `[com.google.javascript/closure-compiler-unshaded "v20180319"]` ([fix commit](https://github.com/google/closure-compiler/commit/c13cf48b98477e44409dba6359246bffa95b1c7b)), will be
|
||||
the default in next ClojureScript release.
|
||||
| `node modules` | `:none` | Requires Cljs 1.10.312 | Supported |
|
||||
| `node modules` | `:advanced` | Requires Cljs 1.10.312 | Supported |
|
||||
|
||||
## Browser - Cljsjs
|
||||
|
||||
|
@ -54,28 +43,23 @@ will in these cases rename the statically object properties, which will break
|
|||
dynamically accessing the objects. Externs fix this by defining which properties
|
||||
must not be renamed.
|
||||
|
||||
## Browser - loading React from CDNJS or custom Webpack bundle
|
||||
## Browser - Webpack
|
||||
|
||||
**TODO: Not tested properly**
|
||||
https://clojurescript.org/guides/webpack
|
||||
|
||||
If you want to load React.js yourself from external JS file (CDN) or from custom bundle,
|
||||
it should be possible to override the Cljsjs foreign-libs, while still using externs from Cljsjs packages. To override the foreign-libs, you can provide following compiler option:
|
||||
|
||||
```clj
|
||||
:foreign-libs
|
||||
[{:file "empty.js",
|
||||
[{:file "bundje.js",
|
||||
:provides ["react" "react-dom" "create-react-class" "react-dom/server"],
|
||||
:requires [],
|
||||
:global-exports {react React
|
||||
react-dom ReactDOM
|
||||
create-react-class createReactClass
|
||||
react-dom/server ReactDOMServer}}]
|
||||
```
|
||||
|
||||
You'll also need to create the mentioned `empty.js` file (FIXME: relative to `project.clj`?).
|
||||
|
||||
If your bundle provides other libraries, you could extern `:provides` and `:global-exports` (e.g. `prop-types`).
|
||||
|
||||
## NodeJS - Cljsjs
|
||||
|
||||
Requires https://github.com/clojure/clojurescript/commit/f7d611d87f6ea8a605eae7c0339f30b79a840b49
|
||||
|
@ -106,3 +90,17 @@ If you have one npm package installed, e.g. `react`, you also need
|
|||
to provide others (`react-dom` and `create-react-class`), else
|
||||
Cljsjs packages would be used for these, and packages from different sources
|
||||
don't work together.
|
||||
|
||||
### Previous problems
|
||||
|
||||
Before ClojureScript 1.10.312 there were couple of problems with npm support:
|
||||
|
||||
1. Closure can't properly handle React 16 CommonJS module pattern: https://github.com/google/closure-compiler/issues/2841
|
||||
This causes the production React code being loaded even for development builds.
|
||||
Using Chrome React Developer Tools with this setup will break Reagent. Fixed by `[com.google.javascript/closure-compiler-unshaded "v20180610"]` ([PR](https://github.com/google/closure-compiler/pull/2963)), will be
|
||||
the included in next the ClojureScript release.
|
||||
|
||||
2. Closure optimization currently breaks certain statically created objects which are
|
||||
accessed dynamically in `ReactDOM/server`: https://github.com/facebook/react/issues/12368
|
||||
Fixed by using `[com.google.javascript/closure-compiler-unshaded "v20180319"]` ([fix commit](https://github.com/google/closure-compiler/commit/c13cf48b98477e44409dba6359246bffa95b1c7b)), will be
|
||||
the default in next ClojureScript release.
|
||||
|
|
|
@ -9,7 +9,7 @@ This is good for all sorts of reasons:
|
|||
* The new code does proper batching of renderings even when changes to atoms are done outside of event handlers (which is great for e.g core.async users).
|
||||
* Repaints can be synced by the browser with for example CSS transitions, since Reagent uses requestAnimationFrame to do the batching. That makes for example animations smoother.
|
||||
|
||||
In short, Reagent renders less often, but at the right times. For a much better description of why async rendering is good, see David Nolen’s [excellent explanation here.](http://swannodette.github.io/2013/12/17/the-future-of-javascript-mvcs/)
|
||||
In short, Reagent renders less often, but at the right times. For a much better description of why async rendering is good, see David Nolen’s [excellent explanation here.](http://swannodette.github.io/2013/12/17/the-future-of-javascript-mvcs)
|
||||
|
||||
## The bad news
|
||||
|
||||
|
|
|
@ -87,6 +87,25 @@ That isn't valid Hiccup and you'll get a slightly baffling error. You'll have to
|
|||
[:div name]]) ;; [:div] containing two nested [:divs]
|
||||
```
|
||||
|
||||
Alternatively, you could return a [React Fragment](https://reactjs.org/docs/fragments.html). In reagent, a React Fragment is created using the `:<>` Hiccup form.
|
||||
|
||||
```cljs
|
||||
(defn right-component
|
||||
[name]
|
||||
[:<>
|
||||
[:div "Hello"]
|
||||
[:div name]])
|
||||
```
|
||||
|
||||
Referring to the example in [React's documentation](https://reactjs.org/docs/fragments.html), the `Columns` component could be defined in reagent as:
|
||||
|
||||
```cljs
|
||||
(defn columns
|
||||
[:<>
|
||||
[:td "Hello"]
|
||||
[:td "World"]]
|
||||
```
|
||||
|
||||
## Form-2: A Function Returning A Function
|
||||
|
||||
Now, let's take one step up in complexity. Sometimes, a component requires:
|
||||
|
@ -143,7 +162,9 @@ In my experience, you'll probably use `Form-3` `components` less than 1% of the
|
|||
|
||||
While the critical part of a component is its render function, sometimes we need to perform actions at various critical moments in a component's lifetime, like when it is first created, or when its about to be destroyed (removed from the DOM), or when its about to be updated, etc.
|
||||
|
||||
With `Form-3` components, you can nominate `lifecycle methods`. reagent provides a very thin layer over React's own `lifecycle methods`. So, before going on, [read all about React's lifecycle methods.](http://facebook.github.io/react/docs/component-specs.html#lifecycle-methods)
|
||||
With `Form-3` components, you can nominate `lifecycle methods`. reagent provides a very thin layer over React's own `lifecycle methods`. So, before going on, [read all about React's lifecycle methods.](http://facebook.github.io/react/docs/component-specs.html#lifecycle-methods).
|
||||
|
||||
Because React's lifecycle methods are object-oriented, they presume the ability to access `this` to obtain the current state of the component. Accordingly, the signatures of the corresponding Reagent lifecycle methods all take a reference to the reagent component as the first argument. This reference can be used with `r/props`, `r/children`, and `r/argv` to obtain the current props/arguments. There are some unexpected details with these functions described below. You may also find `r/dom-node` helpful, as a common use of form-3 components is to draw into a `canvas` element, and you will need access to the underlying DOM element to do so.
|
||||
|
||||
A `Form-3` component definition looks like this:
|
||||
```cljs
|
||||
|
@ -152,15 +173,19 @@ A `Form-3` component definition looks like this:
|
|||
(let [some (local but shared state) ;; <-- closed over by lifecycle fns
|
||||
can (go here)]
|
||||
(reagent/create-class ;; <-- expects a map of functions
|
||||
{:component-did-mount ;; the name of a lifecycle function
|
||||
#(println "component-did-mount") ;; your implementation
|
||||
{:display-name "my-component" ;; for more helpful warnings & errors
|
||||
|
||||
:component-did-mount ;; the name of a lifecycle function
|
||||
(fn [this]
|
||||
(println "component-did-mount")) ;; your implementation
|
||||
|
||||
:component-will-mount ;; the name of a lifecycle function
|
||||
#(println "component-will-mount") ;; your implementation
|
||||
:component-did-update ;; the name of a lifecycle function
|
||||
(fn [this old-argv] ;; reagent provides you the entire "argv", not just the "props"
|
||||
(let [new-argv (rest (reagent/argv this))]
|
||||
(do-something new-argv old-argv)))
|
||||
|
||||
;; other lifecycle funcs can go in here
|
||||
|
||||
:display-name "my-component" ;; for more helpful warnings & errors
|
||||
|
||||
|
||||
:reagent-render ;; Note: is not :render
|
||||
(fn [x y z] ;; remember to repeat parameters
|
||||
|
@ -178,7 +203,13 @@ A `Form-3` component definition looks like this:
|
|||
[my-component 1 2 3]]) ;; Be sure to put the Reagent class in square brackets to force it to render!
|
||||
```
|
||||
|
||||
At the time of writing, the official reagent tutorial doesn't show how to do `Form-3` `components` in the way shown above, and instead suggests that you use `with-meta`, which is clumsy and inferior. So I won't show that method here, but be aware that an alternative way exists to achieve the same outcome.
|
||||
Note the `old-argv` above in the signature for `component-did-mount`. Many of these Reagent lifecycle method analogs take `prev-argv` or `old-argv` (see the docstring for `reagent/create-class` for a full listing). These `argv` arguments include the component constructor as the first argument, which should generally be ignored. This is the same format returned by `(reagent/argv this)`.
|
||||
|
||||
Alternately, you can use `(reagent/props this)` and `(reagent/props children)`, but, conceptually, these don't map as clearly to the `argv` concept. Specifically, the arguments to your render function are actually passed as children (not props) to the underlying React component, **unless the first argument is a map.** If the first argument is a map, then that map is passed as props, and the rest of the arguments are passed as children. Using `props` and `children` may read a bit cleaner, but you do need to pay attention to whether you're passing a props map or not.
|
||||
|
||||
Finally, note that some React lifecycle methods take `prevState` and `nextState`. Because Reagent provides its own state management system, there is no access to these parameters in the lifecycle methods.
|
||||
|
||||
It is possible to create `Form-3` `components` using `with-meta`. However, `with-meta` is a bit clumsy and has no advantages over the above method, but be aware that an alternative way exists to achieve the same outcome.
|
||||
|
||||
**Rookie mistake**
|
||||
|
||||
|
@ -196,6 +227,8 @@ While you can override `component-should-update` to achieve some performance imp
|
|||
|
||||
Leaving out the `:display-name` entry. If you leave it out, Reagent and React have no way of knowing the name of the component causing a problem. As a result, the warnings and errors they generate won't be as informative.
|
||||
|
||||
*****************
|
||||
|
||||
## Final Note
|
||||
|
||||
Above I used the terms `Form-1`, `Form-2` and `Form-3`, but there's actually only one kind of component. It is just that there's **3 different ways to create a component**.
|
||||
|
|
|
@ -30,11 +30,11 @@ We'll start with a code fragment, because it is worth a 1000 words:
|
|||
Notes:
|
||||
1. This example uses a Form-2 component, which allows us to retain state outside of the renderer `fn`. The same technique would work with a Form-3 component.
|
||||
2. We capture state in `!video`. In this example, the state we capture is a reference to a [video HTML element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video).
|
||||
2. `!video` is a `clojure.core/atom` and not a `reaagent.core/atom`. We don't use a normal atom because refs never change during the lifecycle of a component and if we used a reagent atom, it would cause an unnecessary re-render when the ref callback mutates the atom.
|
||||
3. `!video` is a `clojure.core/atom` and not a `reaagent.core/atom`. We use a normal Clojure `atom` because refs never change during the lifecycle of a component and if we used a reagent atom, it would cause an unnecessary re-render when the ref callback mutates the atom.
|
||||
4. On the `:video` component there's a `:ref` callback function which establishes the state in `!video`. You can attach a ref callback to any of the Hiccup elements.
|
||||
5. Thereafter, `@!video` is used with the `:button's` `:on-click` to manipulate the `video` DOM methods.
|
||||
5. For full notes [read Paulus' blog post](https://presumably.de/reagent-mysteries-part-3-manipulating-the-dom.html)
|
||||
6. For more background on callback refs, see [React's documentation](https://facebook.github.io/react/docs/more-about-refs.html)
|
||||
6. For full notes [read Paulus' blog post](https://presumably.de/reagent-mysteries-part-3-manipulating-the-dom.html)
|
||||
7. For more background on callback refs, see [React's documentation](https://facebook.github.io/react/docs/more-about-refs.html)
|
||||
|
||||
***
|
||||
|
||||
|
|
|
@ -94,11 +94,10 @@ Note:
|
|||
Some React libraries use the decorator pattern: a React component which takes a component as an argument and returns a new component as its result. One example is the React DnD library. We will need to use both `adapt-react-class` and `reactify-component` to move back and forth between React and reagent:
|
||||
|
||||
```clojure
|
||||
(defn react-dnd-component
|
||||
[]
|
||||
(def react-dnd-component
|
||||
(let [decorator (DragDropContext HTML5Backend)]
|
||||
[(reagent/adapt-react-class
|
||||
(decorator (reagent/reactify-component top-level-component)))]))
|
||||
(reagent/adapt-react-class
|
||||
(decorator (reagent/reactify-component top-level-component)))))
|
||||
```
|
||||
|
||||
This is the equivalent javascript:
|
||||
|
@ -128,13 +127,13 @@ Some React components expect a function as their only child. React autosizer is
|
|||
|
||||
## Getting props and children of current component
|
||||
|
||||
Because you just pass argument to reagent functions, you typically don't need to think about "props" and "children" as distinct things. But reagent does make a distinction and it is helpful to understand this particularly when interoperating with native elements and React libraries.
|
||||
Because you just pass arguments to reagent functions, you typically don't need to think about "props" and "children" as distinct things. But Reagent does make a distinction and it is helpful to understand this, particularly when interoperating with native elements and React libraries.
|
||||
|
||||
Specifically, if the first argument to your reagent function is a map, that is assigned to `this.props` of the underlying reagent component. All other arguments are assigned as children to `this.props.children`.
|
||||
Specifically, if the first argument to your Reagent function is a map, that is assigned to `this.props` of the underlying Reagent component. All other arguments are assigned as children to `this.props.children`.
|
||||
|
||||
When interacting with native React components, it may be helpful to access props and children, which you can do with `reagent.core/current-component`. This function returns an object that allows you retrieve the props and children passed to the current component.
|
||||
|
||||
Beware that `current-component` is only valid in component functions, and must be called outside of e.g event handlers and for expressions, so it’s safest to always put the call at the top, as in `my-div` here:
|
||||
Beware that `current-component` is only valid in component functions, and must be called outside of e.g event handlers and `for` expressions, so it’s safest to always put the call at the top, as in `my-div` here:
|
||||
|
||||
```clojure
|
||||
(ns example
|
||||
|
@ -154,6 +153,8 @@ Beware that `current-component` is only valid in component functions, and must b
|
|||
|
||||
## React Interop Macros
|
||||
|
||||
**Please do not use these macros. They will be removed at some point. Either use extern inference, externs or proper `goog.object/get`.**
|
||||
|
||||
Reagent provides two utility macros `$` and `$!` for getting and setting javascript properties in a way that is safe for advanced compilation.
|
||||
|
||||
`($ o :foo)` is equivalent to `(.-foo o)`
|
||||
|
@ -164,3 +165,8 @@ Similarly,
|
|||
`($! o :foo 1)` is equivalent to `(set! (.-foo o) 1)`
|
||||
|
||||
Note, these are not necessary if your JavaScript library has an externs file or if externs inference is on and working.
|
||||
|
||||
## Examples
|
||||
|
||||
- [Material-UI](../examples/material-ui/src/example/core.cljs)
|
||||
- [React-sortable-hoc](../examples/react-sortable-hoc/src/example/core.cljs)
|
||||
|
|
|
@ -119,8 +119,8 @@ Cursors are created with `reagent/cursor`, which takes a ratom and a keypath (li
|
|||
```clojure
|
||||
;; First create a ratom
|
||||
(def state (reagent/atom {:foo {:bar "BAR"}
|
||||
:baz "BAZ"
|
||||
:quux "QUUX"}))
|
||||
:baz "BAZ"
|
||||
:quux "QUUX"}))
|
||||
;; Now create a cursor
|
||||
(def bar-cursor (reagent/cursor state [:foo :bar]))
|
||||
|
||||
|
@ -167,7 +167,7 @@ When reactions produce a new result (as determined by `=`), they cause other dep
|
|||
|
||||
The function `make-reaction`, and its macro `reaction` are used to create a `Reaction`, which is a type that belongs to a number of protocols such as `IWatchable`, `IAtom`, `IReactiveAtom`, `IDeref`, `IReset`, `ISwap`, `IRunnable`, etc. which make it atom-like: ie it can be watched, derefed, reset, swapped on, and additionally, tracks its derefs, behave reactively, and so on.
|
||||
|
||||
Reactions are what give `r/atom`, `r/cursor`, and function `r/cursor` and `r/wrap` their power.
|
||||
Reactions are what give `r/atom`, `r/cursor`, and `r/wrap` their power.
|
||||
|
||||
`make-reaction` takes one argument, `f`, and an optional options map. The options map specifies what happens to `f`:
|
||||
|
||||
|
@ -175,8 +175,39 @@ Reactions are what give `r/atom`, `r/cursor`, and function `r/cursor` and `r/wra
|
|||
* `on-set` and `on-dispose` are run when the reaction is set and unset from the DOM
|
||||
* `derefed` **TODO unclear**
|
||||
|
||||
**TODO EXAMPLE**
|
||||
Reactions are very useful when
|
||||
|
||||
* You need a way in which a component only updates based on part of the ratom state. (reagent/cursor can also be used for this scenario)
|
||||
* When you want to combine two `ratoms` and produce a result
|
||||
* You want the component to use some transformed value of `ratom`
|
||||
|
||||
Here's an example:
|
||||
```
|
||||
(def app-state (reagent/atom {:state-var-1 {:var-a 2
|
||||
:var-b 3}
|
||||
:state-var-2 {:var-a 7
|
||||
:var-b 9}}))
|
||||
|
||||
(def app-var2a-reaction (reagent.ratom/make-reaction
|
||||
#(get-in @app-state [:state-var-2 :var-a])))
|
||||
|
||||
|
||||
(defn component-using-make-reaction []
|
||||
[:div
|
||||
[:div "component-using-make-reaction"]
|
||||
[:div "state-var-2 - var-a : " @app-var2a-reaction]])
|
||||
|
||||
```
|
||||
|
||||
The below example uses `reagent.ratom/reaction` macro, which provides syntactic sugar compared to
|
||||
using plain `make-reaction`:
|
||||
|
||||
```
|
||||
(let [username (reagent/atom "")
|
||||
password (reagent/atom "")
|
||||
fields-populated? (reagent.ratom/reaction (every? not-empty [@username @password]))]
|
||||
[:div "Is username and password populated ?" @fields-populated?])
|
||||
```
|
||||
Reactions are executed asynchronously, so be sure to call `flush` if you depend on reaction side effects.
|
||||
|
||||
## The track function
|
||||
|
@ -191,8 +222,8 @@ Here's an example:
|
|||
(ns example.core
|
||||
(:require [reagent.core :as r]))
|
||||
(defonce app-state (r/atom {:people
|
||||
{1 {:name "John Smith"}
|
||||
2 {:name "Maggie Johnson"}}}))
|
||||
{1 {:name "John Smith"}
|
||||
2 {:name "Maggie Johnson"}}}))
|
||||
|
||||
(defn people []
|
||||
(:people @app-state))
|
||||
|
|
|
@ -13,13 +13,15 @@ Also:
|
|||
* [Reagent Deep Dive Series by Timothy Pratley](http://timothypratley.blogspot.com.au/p/p.html) - a four part series
|
||||
* [Reagent Mysteries series by Paulus Esterhazy](https://presumably.de/) - a four part series
|
||||
* [Props, Children & Component Lifecycle](https://www.martinklepsch.org/posts/props-children-and-component-lifecycle-in-reagent.html) by Martin Klepsch
|
||||
* [Using Stateful JS Components - like D3](https://github.com/Day8/re-frame/blob/masterUsing-Stateful-JS-Components.md) (external link)
|
||||
* [Using Stateful JS Components - like D3](https://github.com/Day8/re-frame/blob/master/docs/Using-Stateful-JS-Components.md) (external link)
|
||||
|
||||
## Commercial Videos Series
|
||||
|
||||
* [Learn Reagent Free](https://www.jacekschae.com/learn-reagent-free/tycit?coupon=REAGENT)
|
||||
* [Learn Reagent Pro](https://www.jacekschae.com/learn-reagent-pro/tycit?coupon=REAGENT) (Affiliate link, $30 discount)
|
||||
* [purelyfunctional.tv ](https://purelyfunctional.tv/guide/reagent/)
|
||||
* [Lambda Island Videos](https://lambdaisland.com/collections/react-reagent-re-frame)
|
||||
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
1. [Why isn't my Component re-rendering?](FAQ/ComponentNotRerendering.md)
|
||||
|
@ -31,6 +33,9 @@ Also:
|
|||
5. [How do I force Component re-creation?](https://groups.google.com/forum/#!topic/reagent-project/tNY4gzk7TUY) (external link)
|
||||
6. [How do I access "props" in lifecycle methods?](http://nils-blum-oeste.net/clojurescripts-reagent-using-props-in-lifecycle-hooks/) (external link)
|
||||
|
||||
## Examples
|
||||
|
||||
- [MaterialUI v1 with working TextField](examples/material-ui.md)
|
||||
|
||||
### Want To Add An FAQ?
|
||||
|
||||
|
|
|
@ -52,6 +52,46 @@ As of reagent 0.8.0, the `class` attribute accepts a collection of classes and w
|
|||
[:div {:class ["a-class" (when active? "active") "b-class"]}]
|
||||
```
|
||||
|
||||
## Special notation for id and class
|
||||
|
||||
The id of an element can be indicated with a hash (`#`) after the name of the element.
|
||||
|
||||
This:
|
||||
|
||||
```clojure
|
||||
[:div#my-id]
|
||||
```
|
||||
|
||||
is the same as this:
|
||||
|
||||
```clojure
|
||||
[:div {:id "my-id"}]
|
||||
```
|
||||
|
||||
One or more classes can indicated for an element with a `.` and the class-name like this:
|
||||
|
||||
```clojure
|
||||
[:div.my-class.my-other-class.etc]
|
||||
```
|
||||
|
||||
which is the same as:
|
||||
|
||||
```clojure
|
||||
[:div {:class ["my-class" "my-other-class" "etc"]}]
|
||||
```
|
||||
|
||||
Special notations for id and classes can be used together. The id must be listed first:
|
||||
|
||||
```clojure
|
||||
[:div#my-id.my-class.my-other-class]
|
||||
```
|
||||
|
||||
which is the same as:
|
||||
|
||||
```clojure
|
||||
[:div {:id "my-id" :class ["my-class" "my-other-class"]}]
|
||||
```
|
||||
|
||||
## Special notation for nested elements
|
||||
|
||||
Reagent extends standard Hiccup in one way: it is possible to "squeeze" elements together by using a `>` character.
|
||||
|
|
|
@ -123,8 +123,9 @@ And, finally, a Form-2 parent Component which uses these two child components:
|
|||
(fn parent-renderer
|
||||
[]
|
||||
[:div
|
||||
[more-button counter] ;; no @ on counter
|
||||
[greet-number @counter]]))) ;; notice the @. The prop is an int
|
||||
[greet-number @counter] ;; notice the @. The prop is an int
|
||||
[more-button counter]]))) ;; no @ on counter
|
||||
|
||||
```
|
||||
|
||||
With this setup, answer this question: what rerendering happens each time the `more-button` gets clicked and `counter` gets incremented?
|
||||
|
@ -194,9 +195,9 @@ So this notion of "changed" is pretty important. It controls if we are doing un
|
|||
|
||||
### Lifecycle Functions
|
||||
|
||||
When `props` change, the entire underlying React machinery is engaged. React Components can have lifecycle methods like `component-did-update` and these functions will get called, just as they would if you were dealing with a React Component.
|
||||
When `props` change, the entire underlying React machinery is engaged. Reagent Components can have lifecycle methods like `component-did-update` and these functions will get called, just as they would if you were dealing with a React Component.
|
||||
|
||||
But ... when the rerender is re-run because an input ratom changed, **Lifecycle functions are not run**. So, for example, `component-did-update` will not be called on the Component.
|
||||
But ... when the re-render occurs because an input ratom changed, **Lifecycle functions are not run**. So, for example, `component-did-update` will not be called on the Component.
|
||||
|
||||
Careful of this one. It trips people up.
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
["Why is my attribute (like autoFocus) missing?" {:file "doc/FAQ/MyAttributesAreMissing.md"}]
|
||||
["How can I use React's dangerouslySetInnerHTML?" {:file "doc/FAQ/dangerouslySetInnerHTML.md"}]
|
||||
["Reagent doesn't work after updating dependencies" {:file "doc/FAQ/CljsjsReactProblems.md"}]]
|
||||
["Examples" {}
|
||||
["Material-UI v1" {:file "doc/examples/material-ui.md"}]]
|
||||
["Other" {}
|
||||
["0.8 Upgrade guide" {:file "doc/0.8-upgrade.md"}]
|
||||
["Development guide" {:file "doc/development.md"}]]]}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
# Material-UI
|
||||
|
||||
[Example project](../../examples/material-ui/)
|
||||
|
||||
Material-UI [TextField](https://material-ui.com/api/text-field/) has for long
|
||||
time caused problems for Reagent users. The problem is that `TextField` wraps the
|
||||
`input` element inside a component so that Reagent is not able to enable
|
||||
input cursor fixes, which are required due to [async rendering](http://reagent-project.github.io/news/reagent-is-async.html).
|
||||
|
||||
Good news is that Material-UI v1 has a property that can be used to provide
|
||||
the input component to `TextField`:
|
||||
|
||||
```cljs
|
||||
(ns example.material-ui
|
||||
(:require ["material-ui" :as mui]
|
||||
[reagent.core :as r]))
|
||||
|
||||
(def text-field (r/adapt-react-class mui/TextField))
|
||||
|
||||
(def value (r/atom ""))
|
||||
|
||||
(def input-component
|
||||
(r/reactify-component
|
||||
(fn [props]
|
||||
[:input (-> props
|
||||
(assoc :ref (:inputRef props))
|
||||
(dissoc :inputRef))])))
|
||||
|
||||
(def example []
|
||||
[text-field
|
||||
{:value @value
|
||||
:on-change #(reset! value (.. e -target -value))
|
||||
:InputProps {:inputComponent input-component}}])
|
||||
```
|
||||
|
||||
`reactify-component` can be used to convert Reagent component into React component,
|
||||
which can then be passed into Material-UI. The component should be created once
|
||||
(i.e. on top level) to ensure it is not unnecessarily redefined, causing the
|
||||
component to be re-mounted.
|
||||
For some reason Material-UI uses different name for `ref`, so the `inputRef` property
|
||||
should be renamed by the input component.
|
||||
|
||||
## Wrapping for easy use
|
||||
|
||||
Instead of providing `:InputProps :inputComponent` option to every `TextField`,
|
||||
it is useful to wrap the `TextField` component in a way that the option is added always:
|
||||
|
||||
```cljs
|
||||
(defn text-field [props & children]
|
||||
(let [props (-> props
|
||||
(assoc-in [:InputProps :inputComponent] input-component)
|
||||
rtpl/convert-prop-value)]
|
||||
(apply r/create-element mui/TextField props (map r/as-element children))))
|
||||
```
|
||||
|
||||
Here `r/create-element` and `reagent.impl.template/convert-prop-value` achieve
|
||||
the same as what `adapt-react-class` does, but allows modifying the props.
|
||||
|
||||
**Check the example project for complete code.** Some additional logic is
|
||||
required to ensure option like `:multiline` and `:select` work correctly,
|
||||
as they affect how the `inputComponent` should work.
|
||||
|
||||
TODO: `:multiline` `TextField` without `:rows` (i.e. automatic height) doesn't
|
||||
work, because that requires Material-UI `Input/Textarea`, which doesn't work
|
||||
with Reagent cursor fix.
|
|
@ -0,0 +1,48 @@
|
|||
(defproject material-ui-reagent "0.6.0"
|
||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||
[org.clojure/clojurescript "1.10.439"]
|
||||
[reagent "0.8.1"]
|
||||
[figwheel "0.5.17"]
|
||||
[cljsjs/material-ui "3.2.0-0"]
|
||||
[cljsjs/material-ui-icons "3.0.1-0"]]
|
||||
|
||||
:plugins [[lein-cljsbuild "1.1.7"]
|
||||
[lein-figwheel "0.5.17"]]
|
||||
|
||||
:figwheel {:repl false
|
||||
:http-server-root "public"}
|
||||
|
||||
:profiles {:dev {:resource-paths ["target/cljsbuild/client" "target/cljsbuild/client-npm"]}}
|
||||
|
||||
:cljsbuild
|
||||
{:builds
|
||||
{:client
|
||||
{:source-paths ["src"]
|
||||
:figwheel true
|
||||
:compiler {:parallel-build true
|
||||
:source-map true
|
||||
:optimizations :none
|
||||
:main "example.core"
|
||||
:output-dir "target/cljsbuild/client/public/js/out"
|
||||
:output-to "target/cljsbuild/client/public/js/main.js"
|
||||
:asset-path "js/out"
|
||||
:npm-deps false}}
|
||||
|
||||
;; FIXME: Doesn't work due to Closure bug with scoped npm packages
|
||||
:client-npm
|
||||
{:source-paths ["src"]
|
||||
:figwheel true
|
||||
:compiler {:parallel-build true
|
||||
:source-map true
|
||||
:optimizations :none
|
||||
:main "example.core"
|
||||
:output-dir "target/cljsbuild/client-npm/public/js/out"
|
||||
:output-to "target/cljsbuild/client-npm/public/js/main.js"
|
||||
:asset-path "js/out"
|
||||
:install-deps true
|
||||
:npm-deps {react "16.6.0"
|
||||
react-dom "16.6.0"
|
||||
create-react-class "15.6.3"
|
||||
"@material-ui/core" "3.1.1"
|
||||
"@material-ui/icons" "3.0.1"}
|
||||
:process-shim true}}}})
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<h1>Reagent example app – see README.md</h1>
|
||||
</div>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,171 @@
|
|||
(ns example.core
|
||||
(:require [reagent.core :as r]
|
||||
;; Scoped names require Cljs 1.10.439
|
||||
["@material-ui/core" :as mui]
|
||||
["@material-ui/core/styles" :refer [createMuiTheme withStyles]]
|
||||
["@material-ui/core/colors" :as mui-colors]
|
||||
["@material-ui/icons" :as mui-icons]
|
||||
[goog.object :as gobj]
|
||||
[reagent.impl.template :as rtpl]))
|
||||
|
||||
;; TextField cursor fix:
|
||||
|
||||
(def ^:private input-component
|
||||
(r/reactify-component
|
||||
(fn [props]
|
||||
[:input (-> props
|
||||
(assoc :ref (:inputRef props))
|
||||
(dissoc :inputRef))])))
|
||||
|
||||
(def ^:private textarea-component
|
||||
(r/reactify-component
|
||||
(fn [props]
|
||||
[:textarea (-> props
|
||||
(assoc :ref (:inputRef props))
|
||||
(dissoc :inputRef))])))
|
||||
|
||||
;; To fix cursor jumping when controlled input value is changed,
|
||||
;; use wrapper input element created by Reagent instead of
|
||||
;; letting Material-UI to create input element directly using React.
|
||||
;; Create-element + convert-props-value is the same as what adapt-react-class does.
|
||||
(defn text-field [props & children]
|
||||
(let [props (-> props
|
||||
(assoc-in [:InputProps :inputComponent] (cond
|
||||
(and (:multiline props) (:rows props) (not (:maxRows props)))
|
||||
textarea-component
|
||||
|
||||
;; FIXME: Autosize multiline field is broken.
|
||||
(:multiline props)
|
||||
nil
|
||||
|
||||
;; Select doesn't require cursor fix so default can be used.
|
||||
(:select props)
|
||||
nil
|
||||
|
||||
:else
|
||||
input-component))
|
||||
rtpl/convert-prop-value)]
|
||||
(apply r/create-element mui/TextField props (map r/as-element children))))
|
||||
|
||||
;; Example
|
||||
|
||||
(def custom-theme
|
||||
(createMuiTheme
|
||||
#js {:palette #js {:primary #js {:main (gobj/get (.-red mui-colors) 100)}}}))
|
||||
|
||||
(defn custom-styles [theme]
|
||||
#js {:button #js {:margin (.. theme -spacing -unit)}
|
||||
:textField #js {:width 200
|
||||
:marginLeft (.. theme -spacing -unit)
|
||||
:marginRight (.. theme -spacing -unit)}})
|
||||
|
||||
(def with-custom-styles (withStyles custom-styles))
|
||||
|
||||
(defonce text-state (r/atom "foobar"))
|
||||
|
||||
;; Props in cljs but classes in JS object
|
||||
(defn form [{:keys [classes] :as props}]
|
||||
[:> mui/Grid
|
||||
{:container true
|
||||
:direction "column"
|
||||
:spacing 16}
|
||||
|
||||
[:> mui/Grid {:item true}
|
||||
[:> mui/Toolbar
|
||||
{:disable-gutters true}
|
||||
[:> mui/Button
|
||||
{:variant "contained"
|
||||
:color "primary"
|
||||
:class (.-button classes)
|
||||
:on-click #(swap! text-state str " foo")}
|
||||
"Update value property"
|
||||
[:> mui-icons/AddBox]]
|
||||
|
||||
[:> mui/Button
|
||||
{:variant "outlined"
|
||||
:color "secondary"
|
||||
:class (.-button classes)
|
||||
:on-click #(reset! text-state "")}
|
||||
"Reset"
|
||||
[:> mui-icons/Clear]]]]
|
||||
|
||||
[:> mui/Grid {:item true}
|
||||
[text-field
|
||||
{:value @text-state
|
||||
:label "Text input"
|
||||
:placeholder "Placeholder"
|
||||
:helper-text "Helper text"
|
||||
:class (.-textField classes)
|
||||
:on-change (fn [e]
|
||||
(reset! text-state (.. e -target -value)))
|
||||
:inputRef #(js/console.log "input-ref" %)}]]
|
||||
|
||||
[:> mui/Grid {:item true}
|
||||
[text-field
|
||||
{:value @text-state
|
||||
:label "Textarea"
|
||||
:placeholder "Placeholder"
|
||||
:helper-text "Helper text"
|
||||
:class (.-textField classes)
|
||||
:on-change (fn [e]
|
||||
(reset! text-state (.. e -target -value)))
|
||||
:multiline true
|
||||
;; TODO: Autosize textarea is broken.
|
||||
:rows 10}]]
|
||||
|
||||
[:> mui/Grid {:item true}
|
||||
[text-field
|
||||
{:value @text-state
|
||||
:label "Select"
|
||||
:placeholder "Placeholder"
|
||||
:helper-text "Helper text"
|
||||
:class (.-textField classes)
|
||||
:on-change (fn [e]
|
||||
(reset! text-state (.. e -target -value)))
|
||||
:select true}
|
||||
[:> mui/MenuItem
|
||||
{:value 1}
|
||||
"Item 1"]
|
||||
;; Same as previous, alternative to adapt-react-class
|
||||
[:> mui/MenuItem
|
||||
{:value 2}
|
||||
"Item 2"]]]
|
||||
|
||||
[:> mui/Grid {:item true}
|
||||
[:> mui/Grid
|
||||
{:container true
|
||||
:direction "row"
|
||||
:spacing 8}
|
||||
|
||||
;; For properties that require React Node as parameter,
|
||||
;; either use r/as-element to convert Reagent hiccup forms into React elements,
|
||||
;; or use r/create-element to directly instantiate element from React class (i.e. non-adapted React component).
|
||||
[:> mui/Grid {:item true}
|
||||
[:> mui/Chip
|
||||
{:icon (r/as-element [:> mui-icons/Face])
|
||||
:label "Icon element example, r/as-element"}]]
|
||||
|
||||
[:> mui/Grid {:item true}
|
||||
[:> mui/Chip
|
||||
{:icon (r/create-element mui-icons/Face)
|
||||
:label "Icon element example, r/create-element"}]]]]])
|
||||
|
||||
(defn main []
|
||||
;; fragment
|
||||
[:<>
|
||||
[:> mui/CssBaseline]
|
||||
[:> mui/MuiThemeProvider
|
||||
{:theme custom-theme}
|
||||
[:> mui/Grid
|
||||
{:container true
|
||||
:direction "row"
|
||||
:justify "center"}
|
||||
[:> mui/Grid
|
||||
{:item true
|
||||
:xs 6}
|
||||
[:> (with-custom-styles (r/reactify-component form))]]]]])
|
||||
|
||||
(defn start []
|
||||
(r/render [main] (js/document.getElementById "app")))
|
||||
|
||||
(start)
|
|
@ -0,0 +1,28 @@
|
|||
(defproject reagent/react-sortable-hoc-example "0.1.0"
|
||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||
[org.clojure/clojurescript "1.10.339"]
|
||||
[reagent "0.8.1"]
|
||||
[figwheel "0.5.16"]
|
||||
[cljsjs/react-sortable-hoc "0.8.2-0"]]
|
||||
|
||||
:plugins [[lein-cljsbuild "1.1.7"]
|
||||
[lein-figwheel "0.5.16"]]
|
||||
|
||||
:figwheel {:repl false
|
||||
:http-server-root "public"}
|
||||
|
||||
:profiles {:dev {:resource-paths ["target/cljsbuild/client" "target/cljsbuild/client-npm"]}}
|
||||
|
||||
:cljsbuild
|
||||
{:builds
|
||||
{:client
|
||||
{:source-paths ["src"]
|
||||
:figwheel true
|
||||
:compiler {:parallel-build true
|
||||
:source-map true
|
||||
:optimizations :none
|
||||
:main "example.core"
|
||||
:output-dir "target/cljsbuild/client/public/js/out"
|
||||
:output-to "target/cljsbuild/client/public/js/main.js"
|
||||
:asset-path "js/out"
|
||||
:npm-deps false}}}})
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<h1>Reagent example app – see README.md</h1>
|
||||
</div>
|
||||
<script src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,75 @@
|
|||
(ns example.core
|
||||
(:require [reagent.core :as r]
|
||||
;; FIXME: add global-exports support
|
||||
[cljsjs.react-sortable-hoc]
|
||||
[goog.object :as gobj]))
|
||||
|
||||
;; Adapted from https://github.com/clauderic/react-sortable-hoc/blob/master/examples/drag-handle.js#L10
|
||||
|
||||
(def DragHandle
|
||||
(js/SortableHOC.SortableHandle.
|
||||
;; Alternative to r/reactify-component, which doens't convert props and hiccup,
|
||||
;; is to just provide fn as component and use as-element or create-element
|
||||
;; to return React elements from the component.
|
||||
(fn []
|
||||
(r/as-element [:span "::"]))))
|
||||
|
||||
(def SortableItem
|
||||
(js/SortableHOC.SortableElement.
|
||||
(r/reactify-component
|
||||
(fn [{:keys [value]}]
|
||||
[:li
|
||||
[:> DragHandle]
|
||||
value]))))
|
||||
|
||||
;; Alternative without reactify-component
|
||||
;; props is JS object here
|
||||
#_
|
||||
(def SortableItem
|
||||
(js/SortableHOC.SortableElement.
|
||||
(fn [props]
|
||||
(r/as-element
|
||||
[:li
|
||||
[:> DragHandle]
|
||||
(.-value props)]))))
|
||||
|
||||
(def SortableList
|
||||
(js/SortableHOC.SortableContainer.
|
||||
(r/reactify-component
|
||||
(fn [{:keys [items]}]
|
||||
[:ul
|
||||
(for [[value index] (map vector items (range))]
|
||||
;; No :> or adapt-react-class here because that would convert value to JS
|
||||
(r/create-element
|
||||
SortableItem
|
||||
#js {:key (str "item-" index)
|
||||
:index index
|
||||
:value value}))]))))
|
||||
|
||||
(defn vector-move [coll prev-index new-index]
|
||||
(let [items (into (subvec coll 0 prev-index)
|
||||
(subvec coll (inc prev-index)))]
|
||||
(-> (subvec items 0 new-index)
|
||||
(conj (get coll prev-index))
|
||||
(into (subvec items new-index)))))
|
||||
|
||||
(comment
|
||||
(= [0 2 3 4 1 5] (vector-move [0 1 2 3 4 5] 1 4)))
|
||||
|
||||
(defn sortable-component []
|
||||
(let [items (r/atom (vec (map (fn [i] (str "Item " i)) (range 6))))]
|
||||
(fn []
|
||||
(r/create-element
|
||||
SortableList
|
||||
#js {:items @items
|
||||
:onSortEnd (fn [event]
|
||||
(swap! items vector-move (.-oldIndex event) (.-newIndex event)))
|
||||
:useDragHandle true}))))
|
||||
|
||||
(defn main []
|
||||
[sortable-component])
|
||||
|
||||
(defn start []
|
||||
(r/render [main] (js/document.getElementById "app")))
|
||||
|
||||
(start)
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64">
|
||||
<path stroke="#000" stroke-width="10.751" stroke-linejoin="round" paint-order="markers fill stroke" d="M5.375 5.375h53.249v53.249H5.375z"/>
|
||||
<path d="M16.734 59.233h30.532c1.857 0 3.134-.5 3.714-1.4.58-1 .465-2.1-.58-3.5l-13.35-18.1v-9.1h1.741c.465 0 .813-.1 1.161-.4.348-.3.464-.6.464-1s-.116-.7-.464-1c-.348-.3-.696-.4-1.16-.4H25.208c-.465 0-.813.1-1.161.4-.348.3-.464.6-.464 1s.116.7.464 1c.348.3.696.4 1.16.4h1.742v9.1l-13.35 18.1c-1.045 1.4-1.16 2.5-.58 3.5.696.9 1.973 1.4 3.714 1.4zm13.118-21.5l.58-.7v-9.9H33.8v9.9l.58.7 13.467 18.6H16.502z" fill="#303c3c"/>
|
||||
<path fill="#00d8ff" d="M35.889 42.633h-7.778l-9.055 12.6H45.06z"/>
|
||||
<path d="M180 439.416a5.09 5.09 0 1 1-10.179 0 5.09 5.09 0 1 1 10.179 0z" transform="matrix(.56482 0 0 .5518 -69.11 -222.593)" fill="#00d8ff" stroke="#00d8ff"/>
|
||||
<path d="M37.217 12.657a2.325 2.325 0 1 1-4.65 0 2.325 2.325 0 1 1 4.65 0z" fill="#00d8ff" stroke="#00d8ff" stroke-width=".457"/>
|
||||
<path d="M31.676 6.053a1.17 1.17 0 1 0-2.341 0 1.17 1.17 0 1 0 2.341 0z" fill="#00d8ff" stroke="#00d8ff" stroke-width=".23"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
@ -3,16 +3,16 @@
|
|||
"dependencies": {
|
||||
"@cljs-oss/module-deps": "1.1.1",
|
||||
"create-react-class": "15.6.3",
|
||||
"prop-types": "15.6.1",
|
||||
"react": "16.3.2",
|
||||
"react-dom": "16.3.2"
|
||||
"prop-types": "15.6.2",
|
||||
"react": "16.6.0",
|
||||
"react-dom": "16.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gzip-size-cli": "^2.1.0",
|
||||
"karma": "2.0.2",
|
||||
"gzip-size-cli": "3.0.0",
|
||||
"karma": "3.1.1",
|
||||
"karma-chrome-launcher": "2.2.0",
|
||||
"karma-cljs-test": "0.1.0",
|
||||
"karma-junit-reporter": "1.2.0",
|
||||
"md5-file": "^4.0.0"
|
||||
"md5-file": "4.0.0"
|
||||
}
|
||||
}
|
||||
|
|
27
project.clj
27
project.clj
|
@ -7,15 +7,15 @@
|
|||
;; If :npm-deps enabled, these are used only for externs.
|
||||
;; Without direct react dependency, other packages,
|
||||
;; like react-leaflet might have closer dependency to a other version.
|
||||
[cljsjs/react "16.3.2-0"]
|
||||
[cljsjs/react-dom "16.3.2-0"]
|
||||
[cljsjs/react-dom-server "16.3.2-0"]
|
||||
[cljsjs/create-react-class "15.6.3-0"]]
|
||||
[cljsjs/react "16.6.0-0"]
|
||||
[cljsjs/react-dom "16.6.0-0"]
|
||||
[cljsjs/react-dom-server "16.6.0-0"]
|
||||
[cljsjs/create-react-class "15.6.3-1"]]
|
||||
|
||||
:plugins [[lein-cljsbuild "1.1.7"]
|
||||
[lein-doo "0.1.10"]
|
||||
[lein-codox "0.10.3"]
|
||||
[lein-figwheel "0.5.16"]]
|
||||
[lein-figwheel "0.5.17"]]
|
||||
|
||||
:source-paths ["src"]
|
||||
|
||||
|
@ -23,11 +23,10 @@
|
|||
:exclude clojure.string
|
||||
:source-paths ["src"]}
|
||||
|
||||
:profiles {:dev {:dependencies [[org.clojure/clojurescript "1.10.238"]
|
||||
[figwheel "0.5.16"]
|
||||
:profiles {:dev {:dependencies [[org.clojure/clojurescript "1.10.439"]
|
||||
[figwheel "0.5.17"]
|
||||
[doo "0.1.10"]
|
||||
[com.google.javascript/closure-compiler-unshaded "v20180319"]
|
||||
[cljsjs/prop-types "15.6.1-0"]]
|
||||
[cljsjs/prop-types "15.6.2-0"]]
|
||||
:source-paths ["demo" "test" "examples/todomvc/src" "examples/simple/src" "examples/geometry/src"]
|
||||
:resource-paths ["site" "target/cljsbuild/client" "target/cljsbuild/client-npm"]}}
|
||||
|
||||
|
@ -54,8 +53,8 @@
|
|||
:main "reagentdemo.dev"
|
||||
:output-dir "target/cljsbuild/client/public/js/out"
|
||||
:output-to "target/cljsbuild/client/public/js/main.js"
|
||||
:asset-path "js/out"
|
||||
:npm-deps false}}
|
||||
:npm-deps false
|
||||
:asset-path "js/out"}}
|
||||
|
||||
{:id "client-npm"
|
||||
:source-paths ["demo"]
|
||||
|
@ -66,6 +65,7 @@
|
|||
:main "reagentdemo.dev"
|
||||
:output-dir "target/cljsbuild/client-npm/public/js/out"
|
||||
:output-to "target/cljsbuild/client-npm/public/js/main.js"
|
||||
:npm-deps true
|
||||
:asset-path "js/out"}}
|
||||
|
||||
{:id "test"
|
||||
|
@ -87,6 +87,7 @@
|
|||
:asset-path "js/out"
|
||||
:output-dir "target/cljsbuild/test-npm/out"
|
||||
:output-to "target/cljsbuild/test-npm/main.js"
|
||||
:npm-deps true
|
||||
:aot-cache true}}
|
||||
|
||||
;; Separate source-path as this namespace uses Node built-in modules which
|
||||
|
@ -97,6 +98,7 @@
|
|||
:target :nodejs
|
||||
:output-dir "target/cljsbuild/prerender/out"
|
||||
:output-to "target/cljsbuild/prerender/main.js"
|
||||
:npm-deps true
|
||||
:aot-cache true}}
|
||||
|
||||
{:id "node-test"
|
||||
|
@ -120,6 +122,7 @@
|
|||
:optimizations :none
|
||||
:output-dir "target/cljsbuild/node-test-npm/out"
|
||||
:output-to "target/cljsbuild/node-test-npm/main.js"
|
||||
:npm-deps true
|
||||
:aot-cache true}}
|
||||
|
||||
;; With :advanched source-paths doesn't matter that much as
|
||||
|
@ -147,6 +150,7 @@
|
|||
:output-to "target/cljsbuild/prod-npm/public/js/main.js"
|
||||
:output-dir "target/cljsbuild/prod-npm/out" ;; Outside of public, not published
|
||||
:closure-warnings {:global-this :off}
|
||||
:npm-deps true
|
||||
:aot-cache true}}
|
||||
|
||||
{:id "prod-test"
|
||||
|
@ -171,4 +175,5 @@
|
|||
:output-to "target/cljsbuild/prod-test-npm/main.js"
|
||||
:output-dir "target/cljsbuild/prod-test-npm/out"
|
||||
:closure-warnings {:global-this :off}
|
||||
:npm-deps true
|
||||
:aot-cache true}}]})
|
||||
|
|
|
@ -86,7 +86,14 @@
|
|||
|
||||
;;; Rendering
|
||||
|
||||
(defn wrap-render [c]
|
||||
(defn wrap-render
|
||||
"Calls the render function of the component `c`. If result `res` evaluates to a:
|
||||
1) Vector (form-1 component) - Treats the vector as hiccup and returns
|
||||
a react element with a render function based on that hiccup
|
||||
2) Function (form-2 component) - updates the render function to `res` i.e. the internal function
|
||||
and calls wrap-render again (`recur`), until the render result doesn't evaluate to a function.
|
||||
3) Anything else - Returns the result of evaluating `c`"
|
||||
[c]
|
||||
(let [f ($ c :reagentRender)
|
||||
_ (assert-callable f)
|
||||
res (if (true? ($ c :cljsLegacyRender))
|
||||
|
|
|
@ -323,9 +323,10 @@
|
|||
(let [props (nth argv 1 nil)
|
||||
hasprops (or (nil? props) (map? props))
|
||||
jsprops (convert-prop-value (if hasprops props))
|
||||
jsprops (if-some [key (key-from-vec argv)]
|
||||
(oset jsprops "key" key)
|
||||
jsprops)
|
||||
first-child (+ 1 (if hasprops 1 0))]
|
||||
(when-some [key (key-from-vec argv)]
|
||||
(oset jsprops "key" key))
|
||||
(make-element argv react/Fragment jsprops first-child)))
|
||||
|
||||
(defn adapt-react-class
|
||||
|
@ -343,20 +344,20 @@
|
|||
(aset tag-name-cache x (parse-tag x))))
|
||||
|
||||
(defn native-element [parsed argv first]
|
||||
(let [comp ($ parsed :name)]
|
||||
(let [props (nth argv first nil)
|
||||
hasprops (or (nil? props) (map? props))
|
||||
jsprops (convert-props (if hasprops props) parsed)
|
||||
first-child (+ first (if hasprops 1 0))]
|
||||
(if (input-component? comp)
|
||||
(-> [(reagent-input) argv comp jsprops first-child]
|
||||
(with-meta (meta argv))
|
||||
as-element)
|
||||
(let [key (-> (meta argv) get-key)
|
||||
p (if (nil? key)
|
||||
jsprops
|
||||
(oset jsprops "key" key))]
|
||||
(make-element argv comp p first-child))))))
|
||||
(let [comp ($ parsed :name)
|
||||
props (nth argv first nil)
|
||||
hasprops (or (nil? props) (map? props))
|
||||
jsprops (convert-props (if hasprops props) parsed)
|
||||
first-child (+ first (if hasprops 1 0))]
|
||||
(if (input-component? comp)
|
||||
(-> [(reagent-input) argv comp jsprops first-child]
|
||||
(with-meta (meta argv))
|
||||
as-element)
|
||||
(let [key (-> (meta argv) get-key)
|
||||
p (if (nil? key)
|
||||
jsprops
|
||||
(oset jsprops "key" key))]
|
||||
(make-element argv comp p first-child)))))
|
||||
|
||||
(defn str-coll [coll]
|
||||
(if (dev?)
|
||||
|
|
|
@ -33,10 +33,20 @@
|
|||
false))))))
|
||||
|
||||
(defn- in-context [obj f]
|
||||
"When f is executed, if (f) derefs any ratoms, they are then added to 'obj.captured'(*ratom-context*).
|
||||
|
||||
See function notify-deref-watcher! to know how *ratom-context* is updated"
|
||||
(binding [*ratom-context* obj]
|
||||
(f)))
|
||||
|
||||
(defn- deref-capture [f r]
|
||||
(defn- deref-capture
|
||||
"Returns `(in-context f r)`. Calls `_update-watching` on r with any
|
||||
`deref`ed atoms captured during `in-context`, if any differ from the
|
||||
`watching` field of r. Clears the `dirty?` flag on r.
|
||||
|
||||
Inside '_update-watching' along with adding the ratoms in 'r.watching' of reaction,
|
||||
the reaction is also added to the list of watches on each ratoms f derefs."
|
||||
[f r]
|
||||
(set! (.-captured r) nil)
|
||||
(when (dev?)
|
||||
(set! (.-ratomGeneration r) (set! generation (inc generation))))
|
||||
|
@ -48,7 +58,11 @@
|
|||
(._update-watching r c))
|
||||
res))
|
||||
|
||||
(defn- notify-deref-watcher! [derefed]
|
||||
(defn- notify-deref-watcher!
|
||||
"Add `derefed` to the `captured` field of `*ratom-context*`.
|
||||
|
||||
See also `in-context`"
|
||||
[derefed]
|
||||
(when-some [r *ratom-context*]
|
||||
(let [c (.-captured r)]
|
||||
(if (nil? c)
|
||||
|
@ -337,7 +351,15 @@
|
|||
(defn- handle-reaction-change [this sender old new]
|
||||
(._handle-change this sender old new))
|
||||
|
||||
|
||||
;; Fields of a Reaction javascript object
|
||||
;; - auto_run
|
||||
;; - captured
|
||||
;; - caught
|
||||
;; - f
|
||||
;; - ratomGeneration
|
||||
;; - state
|
||||
;; - watches
|
||||
;; - watching
|
||||
(deftype Reaction [f ^:mutable state ^:mutable ^boolean dirty? ^boolean nocache?
|
||||
^:mutable watching ^:mutable watches ^:mutable auto-run
|
||||
^:mutable caught]
|
||||
|
@ -499,7 +521,16 @@
|
|||
|
||||
(def ^:private temp-reaction (make-reaction nil))
|
||||
|
||||
(defn run-in-reaction [f obj key run opts]
|
||||
|
||||
(defn run-in-reaction
|
||||
"Evaluates `f` and returns the result. If `f` calls `deref` on any ratoms,
|
||||
creates a new Reaction that watches those atoms and calls `run` whenever
|
||||
any of those watched ratoms change. Also, the new reaction is added to
|
||||
list of 'watches' of each of the ratoms. The `run` parameter is a function
|
||||
that should expect one argument. It is passed `obj` when run. The `opts`
|
||||
are any options accepted by a Reaction and will be set on the newly created
|
||||
Reaction. Sets the newly created Reaction to the `key` on `obj`."
|
||||
[f obj key run opts]
|
||||
(let [r temp-reaction
|
||||
res (deref-capture f r)]
|
||||
(when-not (nil? (.-watching r))
|
||||
|
|
|
@ -1189,8 +1189,12 @@
|
|||
[:div "hello"]
|
||||
[:div "world"]]
|
||||
^{:key 2}
|
||||
[children])])]
|
||||
(is (= "<div><div>hello</div><div>world</div><div>foo</div></div>"
|
||||
[children]
|
||||
^{:key 3}
|
||||
[:<>
|
||||
[:div "1"]
|
||||
[:div "2"]])])]
|
||||
(is (= "<div><div>hello</div><div>world</div><div>foo</div><div>1</div><div>2</div></div>"
|
||||
(as-string [comp]))))))
|
||||
|
||||
(defonce my-context (react/createContext "default"))
|
||||
|
|
Loading…
Reference in New Issue