├── .clj-kondo
├── config.edn
└── funcool
│ └── promesa
│ └── config.edn
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── issue_template.md
└── workflows
│ └── clojure.yml
├── .gitignore
├── .lsp
└── config.edn
├── .nvmrc
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── ROADMAP.md
├── SECURITY.md
├── bb.edn
├── build-docs.sh
├── build-example-site.sh
├── codecov.yml
├── demo
├── reagentdemo
│ ├── common.cljs
│ ├── core.cljs
│ ├── dev.cljs
│ ├── intro.cljs
│ ├── news.cljs
│ ├── news
│ │ ├── anyargs.cljs
│ │ ├── async.cljs
│ │ ├── binaryclock.cljs
│ │ ├── clockpost.cljs
│ │ ├── news050.cljs
│ │ ├── news051.cljs
│ │ ├── news060.cljs
│ │ ├── news060rc.cljs
│ │ ├── news060release.cljs
│ │ ├── news061.cljs
│ │ └── undodemo.cljs
│ ├── prod.cljs
│ ├── syntax.clj
│ └── syntax.cljs
└── sitetools
│ └── core.cljs
├── deps.edn
├── doc
├── 0.8-upgrade.md
├── BatchingAndTiming.md
├── ControlledInputs.md
├── CreatingReagentComponents.md
├── FAQ
│ ├── CljsjsReactProblems.md
│ ├── ComponentNotRerendering.md
│ ├── ForcingComponentRecreation.md
│ ├── HtmlEntities.md
│ ├── MyAttributesAreMissing.md
│ ├── UsingAnEntity.md
│ ├── UsingRefs.md
│ └── dangerouslySetInnerHTML.md
├── InteropWithReact.md
├── ManagingState.md
├── README.md
├── ReactFeatures.md
├── ReactRenderers.md
├── ReagentCompiler.md
├── Security.md
├── ServerSideRendering.md
├── UsingHiccupToDescribeHTML.md
├── UsingSquareBracketsInsteadOfParens.md
├── WhenDoComponentsUpdate.md
├── benchmark.png
├── cljdoc.edn
├── development.md
└── examples
│ ├── material-ui.md
│ └── smooth-ui.md
├── examples
├── functional-components-and-hooks
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── resources
│ │ └── public
│ │ │ ├── example.css
│ │ │ └── index.html
│ ├── shadow-cljs.edn
│ └── src
│ │ └── example
│ │ └── core.cljs
├── geometry
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── resources
│ │ └── public
│ │ │ ├── example.css
│ │ │ └── index.html
│ ├── shadow-cljs.edn
│ └── src
│ │ └── geometry
│ │ ├── components.cljs
│ │ ├── core.cljs
│ │ └── geometry.cljs
├── material-ui
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── resources
│ │ └── public
│ │ │ └── index.html
│ ├── shadow-cljs.edn
│ └── src
│ │ └── example
│ │ └── core.cljs
├── react-context
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── resources
│ │ └── public
│ │ │ └── index.html
│ ├── shadow-cljs.edn
│ └── src
│ │ └── example
│ │ └── core.cljs
├── react-mde
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── resources
│ │ └── public
│ │ │ ├── index.html
│ │ │ └── react-mds-all.css
│ ├── shadow-cljs.edn
│ └── src
│ │ └── example
│ │ └── core.cljs
├── react-sortable-hoc
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── resources
│ │ └── public
│ │ │ └── index.html
│ ├── shadow-cljs.edn
│ └── src
│ │ └── example
│ │ └── core.cljs
├── react-transition-group
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── resources
│ │ └── public
│ │ │ ├── fade.css
│ │ │ └── index.html
│ ├── shadow-cljs.edn
│ └── src
│ │ └── example
│ │ └── core.cljs
├── simple
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── resources
│ │ └── public
│ │ │ ├── example.css
│ │ │ └── index.html
│ ├── shadow-cljs.edn
│ └── src
│ │ └── simpleexample
│ │ └── core.cljs
└── todomvc
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── resources
│ └── public
│ │ ├── index.html
│ │ └── todos.css
│ ├── shadow-cljs.edn
│ └── src
│ └── todomvc
│ └── core.cljs
├── lib
└── modules.js
├── logo
├── logo-text.png
├── logo-text.svg
├── logo.png
└── logo.svg
├── outsite
└── public
│ └── README.md
├── package-lock.json
├── package.json
├── prepare-tests.sh
├── prerender
└── sitetools
│ └── prerender.cljs
├── project.clj
├── run-tests.sh
├── shadow-cljs.edn
├── site
└── public
│ ├── css
│ ├── examples.css
│ └── main.css
│ └── index.html
├── src
├── clj-kondo
│ └── clj-kondo.exports
│ │ └── reagent
│ │ └── reagent
│ │ └── config.edn
└── reagent
│ ├── core.clj
│ ├── core.cljs
│ ├── debug.clj
│ ├── debug.cljs
│ ├── dom.cljs
│ ├── dom
│ ├── client.cljs
│ └── server.cljs
│ ├── impl
│ ├── batching.cljs
│ ├── component.cljs
│ ├── input.cljs
│ ├── protocols.cljs
│ ├── template.cljs
│ └── util.cljs
│ ├── interop.clj
│ ├── interop.cljs
│ ├── ratom.clj
│ └── ratom.cljs
├── test-environments
├── browser-cljsjs-prod
│ └── test.sh
├── browser-cljsjs
│ └── test.sh
├── browser-npm-prod
│ └── test.sh
├── browser-npm
│ └── test.sh
├── bundle-adv
│ ├── README.md
│ ├── build.edn
│ ├── build.sh
│ ├── karma.conf.js
│ ├── karma.edn
│ ├── test.sh
│ └── webpack.config.js
├── bundle
│ ├── build.edn
│ ├── karma.conf.js
│ ├── test.sh
│ ├── webpack.config.js
│ └── workaround.js
├── node-cljsjs
│ └── test_disabled.sh
├── node-npm
│ └── test.sh
└── shadow-cljs-prod
│ ├── karma.conf.js
│ └── test.sh
└── test
├── reagent
└── impl
│ ├── template_test.cljs
│ └── util_test.cljs
├── reagenttest
├── performancetest.cljs
├── runtests.cljs
├── testcursor.cljs
├── testratom.cljs
├── testratomasync.cljs
├── testreagent.cljs
├── testtrack.cljs
├── testwithlet.cljs
├── testwrap.cljs
├── utils.clj
└── utils.cljs
└── runners
└── karma.conf.js
/.clj-kondo/config.edn:
--------------------------------------------------------------------------------
1 | {:lint-as {reagent.core/with-let clojure.core/let
2 | reagenttest.utils/deftest clojure.test/deftest
3 | reagenttest.utils/with-render clojure.core/let}
4 | :linters {:unused-binding {:level :off}
5 | :missing-else-branch {:level :off}
6 | :unused-referred-var {:exclude {cljs.test [deftest testing is]}}
7 | ;; Example namespaces use clashing names now
8 | :redefined-var {:level :off}}}
9 |
--------------------------------------------------------------------------------
/.clj-kondo/funcool/promesa/config.edn:
--------------------------------------------------------------------------------
1 | {:lint-as {promesa.core/-> clojure.core/->
2 | promesa.core/->> clojure.core/->>
3 | promesa.core/as-> clojure.core/as->
4 | promesa.core/let clojure.core/let
5 | promesa.core/plet clojure.core/let
6 | promesa.core/loop clojure.core/loop
7 | promesa.core/recur clojure.core/recur
8 | promesa.core/with-redefs clojure.core/with-redefs
9 | promesa.core/doseq clojure.core/doseq}}
10 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | Please check existing issues first if they have already been reported. Check [controlled inputs issue](https://github.com/reagent-project/reagent/issues/619), especially for all problems related to the controlled inputs workaround.
11 |
12 | **Describe the bug**
13 | A clear and concise description of what the bug is.
14 |
15 | **To Reproduce**
16 | Steps to reproduce the behavior:
17 |
18 | **Expected behavior**
19 | A clear and concise description of what you expected to happen.
20 |
21 | **Environment:**
22 | - Reagent version
23 | - ClojureScript and build tooling version
24 | - Browser and version
25 |
26 | **Additional context**
27 | Please feel free to add any other context about the problem here.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | You can open an issue if you have a clear idea of how it would work. If you have less idea of how the idea would work, consider using [discussions](https://github.com/reagent-project/reagent/discussions) to get feedback if the idea is feasible to implement.
11 |
12 | **Is your feature request related to a problem? Please describe.**
13 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
14 |
15 | **Describe the solution you'd like**
16 | A clear and concise description of what you want to happen.
17 |
18 | **Describe alternatives you've considered**
19 | A clear and concise description of any alternative solutions or features you've considered.
20 |
21 | **Additional context**
22 | Add any other context or screenshots about the feature request here.
23 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 | No support questions in here please. This is a place for bug reports and feature requests only.
2 |
3 | Support requests and usage questions should go to the #reagent channel in [Clojure Slack](http://clojurians.net/) or the [ClojureScript mailing list](https://groups.google.com/forum/#!forum/clojurescript).
4 |
--------------------------------------------------------------------------------
/.github/workflows/clojure.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: Run tests
3 |
4 | on: [push, pull_request]
5 |
6 | jobs:
7 | test:
8 | strategy:
9 | matrix:
10 | env:
11 | - browser-cljsjs
12 | - browser-cljsjs-prod
13 | - browser-npm
14 | - browser-npm-prod
15 | - bundle
16 | - bundle-adv
17 | # - node-cljsjs
18 | - node-npm
19 | - shadow-cljs-prod
20 |
21 | name: Test ${{ matrix.env }}
22 |
23 | runs-on: ubuntu-latest
24 | env:
25 | ENV: ${{ matrix.env }}
26 |
27 | steps:
28 | - uses: actions/checkout@v4
29 | - name: Setup Java
30 | uses: actions/setup-java@v4
31 | with:
32 | distribution: 'adopt'
33 | java-version: 17
34 | - name: Setup Clojure
35 | uses: DeLaGuardo/setup-clojure@master
36 | with:
37 | lein: 2.9.10
38 | cli: latest
39 | - name: Setup Node.js
40 | uses: actions/setup-node@v4
41 | with:
42 | node-version: lts/hydrogen
43 | cache: 'npm'
44 |
45 | # setup-java cache only looks at pom.xml for the key
46 | - name: Cache m2
47 | uses: actions/cache@v4
48 | with:
49 | path: ~/.m2
50 | key: ${{ runner.os }}-m2-${{ hashFiles('**/project.clj') }}
51 | restore-keys: |
52 | ${{ runner.os }}-m2-
53 |
54 | - name: Install node deps
55 | run: npm ci
56 |
57 | - name: Run tests
58 | run: ./test-environments/${{ matrix.env }}/test.sh
59 |
60 | - uses: codecov/codecov-action@v5
61 | with:
62 | env_vars: ENV
63 |
64 | update-site:
65 | name: Update site
66 | runs-on: ubuntu-latest
67 | if: ${{ github.ref == 'refs/heads/master' }}
68 | needs: test
69 | environment: build-site
70 | steps:
71 | - uses: actions/checkout@v4
72 | - name: Setup Java
73 | uses: actions/setup-java@v4
74 | with:
75 | distribution: 'adopt'
76 | java-version: 17
77 | - name: Setup Clojure
78 | uses: DeLaGuardo/setup-clojure@master
79 | with:
80 | lein: 2.9.8
81 | cli: latest
82 | - name: Setup Node.js
83 | uses: actions/setup-node@v4
84 | with:
85 | node-version: lts/hydrogen
86 | cache: 'npm'
87 |
88 | # setup-java cache only looks at pom.xml for the key
89 | - name: Cache m2
90 | uses: actions/cache@v4
91 | with:
92 | path: ~/.m2
93 | key: ${{ runner.os }}-m2-${{ hashFiles('**/project.clj') }}
94 | restore-keys: |
95 | ${{ runner.os }}-m2-
96 |
97 | - name: Install node deps
98 | run: npm ci
99 |
100 | - name: Build site
101 | run: ./build-example-site.sh
102 | env:
103 | SITE_TOKEN: ${{ secrets.SITE_TOKEN }}
104 |
105 | update-tagged-docs:
106 | name: Update docs
107 | runs-on: ubuntu-latest
108 | if: ${{ startsWith(github.ref, 'refs/tags/') }}
109 | needs: test
110 | steps:
111 | - uses: actions/checkout@v4
112 | - name: Setup Java
113 | uses: actions/setup-java@v4
114 | with:
115 | distribution: 'zulu'
116 | java-version: 8
117 | - name: Setup Clojure
118 | uses: DeLaGuardo/setup-clojure@master
119 | with:
120 | lein: 2.9.8
121 | cli: latest
122 |
123 | # setup-java cache only looks at pom.xml for the key
124 | - name: Cache m2
125 | uses: actions/cache@v4
126 | with:
127 | path: ~/.m2
128 | key: ${{ runner.os }}-m2-${{ hashFiles('**/project.clj') }}
129 | restore-keys: |
130 | ${{ runner.os }}-m2-
131 |
132 | - name: Build docs
133 | run: ./build-docs.sh
134 | # Fine grained personal access token, 366 days expiry, only contents rw permission to the site repo
135 | env:
136 | SITE_TOKEN: ${{ secrets.SITE_TOKEN }}
137 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | pom.xml
3 | .lein-repl-history
4 | pom.xml.asc
5 | .nrepl-port
6 | demo/empty.cljs
7 | out
8 | tmp
9 | figwheel_server.log
10 |
11 | reagent.iml
12 | .idea
13 | .lein-failures
14 |
15 | node_modules
16 | junit
17 | .shadow-cljs
18 | coverage/
19 |
20 | .clj-kondo/.cache
21 | .cpcache/
22 | .lsp/.cache
23 | .nrepl.edn
24 |
--------------------------------------------------------------------------------
/.lsp/config.edn:
--------------------------------------------------------------------------------
1 | {:clean {:automatically-after-ns-refactor true
2 | :ns-inner-blocks-indentation :same-line
3 | :sort {:require :lexicographically}}
4 | :cljfmt {:function-arguments-indentation :cursive}}
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/hydrogen
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Reagent
2 |
3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
4 |
5 | The following is a set of guidelines for contributing to Reagent which is hosted on [Github](fram://github.com/reagent-project/reagent).
6 | These are just guidelines, not rules. Use your best judgment and feel free to propose changes to this document in a pull request.
7 |
8 | ## Support questions
9 |
10 | **The Github issues for Reagent are for bug reports and feature requests. Support requests and usage questions should go to the [Clojure Slack channel](http://clojurians.net), [Clojureverse discussion board](https://clojureverse.org/), the [ClojureScript mailing list](https://groups.google.com/forum/#!forum/clojurescript), or the [Reagent mailing list](https://groups.google.com/forum/#!forum/reagent-project).**
11 |
12 | ## Creating issues for bugs
13 |
14 | Check if the issue has already been reported. If possible provide:
15 |
16 | * Version of Reagent being used
17 | * Minimal reproduction steps
18 |
19 | ## Creating issues for features
20 |
21 | Use your best judgement on what is needed here.
22 |
23 | ## Pull requests
24 |
25 | **Create pull requests to the master branch**.
26 |
27 | Check [development docs](./doc/development.md)
28 |
29 | ## Pull requests for bugs
30 |
31 | If possible provide:
32 |
33 | * Code that fixes the bug
34 | * Failing tests which pass with the new changes
35 | * Improvements to documentation to make it less likely that others will run into issues (if relevant).
36 | * Add the change to the Unreleased section of [CHANGELOG.md](CHANGELOG.md)
37 |
38 | ## Pull requests for features
39 |
40 | If possible provide:
41 |
42 | * Code that implements the new feature
43 | * Tests to cover the new feature including all of the code paths
44 | * Docstrings for functions
45 | * Documentation examples
46 | * Add the change to the Unreleased section of [CHANGELOG.md](CHANGELOG.md)
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2013-2017 Dan Holmsand
4 | Copyright (c) 2017 Reagent contributors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/ROADMAP.md:
--------------------------------------------------------------------------------
1 | # Maintainer's roadmap
2 |
3 | - Replace Doo with Kaocha-cljs2 or other more modern cljs test runner
4 | - Make functional components the default
5 | - Build a new site
6 | - Make Reagent React 18 and Concurrent Mode compatible: [#554](https://github.com/reagent-project/reagent/pull/554)
7 | - New API to use regular atoms as ratoms: [#549](https://github.com/reagent-project/reagent/issues/546)
8 | - Add React hook for using Ratoms/reactions through a hook: [#545](https://github.com/reagent-project/reagent/issues/545)
9 | - Needs React 18 for `useSyncExternalStore`
10 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Reporting a Vulnerability
4 |
5 | If you have found an issue that causes a vulnerability and should be reported without making it public, you can contact [Juho Teperi](https://github.com/Deraen) over email or Clojurians Slack.
6 |
--------------------------------------------------------------------------------
/bb.edn:
--------------------------------------------------------------------------------
1 | {:tasks {figwheel {:task (shell "lein figwheel")}
2 | shadow {:task (shell "npx shadow-cljs watch client")}}}
3 |
--------------------------------------------------------------------------------
/build-docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | TAG=${GITHUB_REF/refs\/tags\//}
6 |
7 | if [[ -z $TAG ]]; then
8 | echo "Set TAG"
9 | exit 1
10 | fi
11 |
12 | lein codox
13 |
14 | rm -fr tmp
15 | if [[ -n $GITHUB_ACTOR ]]; then
16 | git config --global user.email "14146879+github-actions[bot]@users.noreply.github.com"
17 | git config --global user.name "github-actions[bot]"
18 | git clone "https://${GITHUB_ACTOR}:${SITE_TOKEN}@github.com/reagent-project/reagent-project.github.io.git" tmp
19 | else
20 | git clone git@github.com:reagent-project/reagent-project.github.io.git tmp
21 | fi
22 |
23 | mkdir -p "tmp/docs/$TAG/"
24 | cp -r target/doc/* "tmp/docs/$TAG/"
25 |
26 | cd tmp
27 | git add .
28 | git commit -m "Built docs from $TAG"
29 | git push
30 | rm -rf tmp
31 |
--------------------------------------------------------------------------------
/build-example-site.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | SHA=$(git rev-parse HEAD)
6 |
7 | lein 'do' clean, cljsbuild once prod-npm, cljsbuild once prerender
8 |
9 | node target/cljsbuild/prerender/main.js target/cljsbuild/prod-npm/public/
10 |
11 | lein codox
12 |
13 | rm -fr tmp
14 | if [[ -n $GITHUB_ACTOR ]]; then
15 | git config --global user.email "14146879+github-actions[bot]@users.noreply.github.com"
16 | git config --global user.name "github-actions[bot]"
17 | git clone "https://${GITHUB_ACTOR}:${SITE_TOKEN}@github.com/reagent-project/reagent-project.github.io.git" tmp
18 | else
19 | git clone git@github.com:reagent-project/reagent-project.github.io.git tmp
20 | fi
21 |
22 | # Remove everything to ensure old files are removed
23 | rm -fr tmp/*
24 |
25 | cp -r target/cljsbuild/prod-npm/public/* tmp/
26 | cp -r target/prerender/public/* tmp/
27 | mkdir -p tmp/docs/master/
28 | cp -r target/doc/* tmp/docs/master/
29 |
30 | test -f tmp/index.html
31 | test -f tmp/js/main.js
32 | test ! -e tmp/js/out
33 |
34 | cd tmp
35 |
36 | # Restore files not created by this script
37 | git add docs/master/
38 | git checkout -- README.md docs/
39 | git add .
40 | git commit -m "Built site from $SHA"
41 | git push
42 | rm -rf tmp
43 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | ---
2 | coverage:
3 |  status:
4 |  patch:
5 |  default:
6 |  enabled: false
7 |
--------------------------------------------------------------------------------
/demo/reagentdemo/common.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.common
2 | (:require [reagent.core :as r]))
3 |
4 | (defn demo-component [{:keys [comp src complete no-heading]}]
5 | (r/with-let [showing (r/atom true)]
6 | [:div
7 | (when comp
8 | [:div.demo-example.clearfix
9 | [:a.demo-example-hide {:on-click (fn [e]
10 | (.preventDefault e)
11 | (swap! showing not)
12 | nil)}
13 | (if @showing "hide" "show")]
14 | (when-not no-heading
15 | [:h3.demo-heading "Example "])
16 | (when @showing
17 | (if-not complete
18 | [:div.simple-demo [comp]]
19 | [comp]))])
20 | (if @showing
21 | (if src
22 | [:div.demo-source.clearfix
23 | (when-not no-heading
24 | [:h3.demo-heading "Source"])
25 | src]
26 | [:div.clearfix]))]))
27 |
--------------------------------------------------------------------------------
/demo/reagentdemo/core.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.core
2 | (:require [reagent.core :as r]
3 | [sitetools.core :as tools :refer [link]]
4 | [reagentdemo.intro :as intro]
5 | [reagentdemo.news :as news]))
6 |
7 | (def github {:href "https://github.com/reagent-project/reagent"})
8 |
9 | (defn github-badge []
10 | [:a.github-badge
11 | github
12 | [:img {:style {:position "absolute" :top 0 :left 0 :border 0}
13 | :alt "Fork me on GitHub"
14 | :src "https://s3.amazonaws.com/github/ribbons/forkme_left_orange_ff7600.png"}]])
15 |
16 | (def index-page "/index.html")
17 | (def title "Minimalistic React for ClojureScript")
18 |
19 | (tools/register-page index-page [#'intro/main] title)
20 |
21 | (defn error-boundary [& children]
22 | (let [error (r/atom nil)]
23 | (r/create-class
24 | {:constructor (fn [this props])
25 | :component-did-catch (fn [this e info])
26 | :get-derived-state-from-error (fn [e]
27 | (reset! error e)
28 | #js {})
29 | :reagent-render (fn [& children]
30 | (if @error
31 | [:div
32 | "Something went wrong."
33 | [:input
34 | {:type "button"
35 | :on-click #(reset! error nil)
36 | :value "Try again"}]]
37 | (into [:<>] children)))})))
38 |
39 | (defn demo [& [test-component]]
40 | [error-boundary
41 | [:div
42 | [:div.nav>ul.nav
43 | [:li.brand [link {:href index-page} "Reagent:"]]
44 | [:li [link {:href index-page} "Intro"]]
45 | [:li [link {:href news/url} "News"]]
46 | [:li>a github "GitHub"]
47 | [:li [:a {:href "http://reagent-project.github.io/docs/master/"} "API"]]]
48 | [:div test-component]
49 | [tools/main-content]
50 | [github-badge]]])
51 |
52 | (defn init! [& [test-component]]
53 | (tools/start! {:body [#'demo test-component]
54 | :title-prefix "Reagent: "
55 | :css-infiles ["site/public/css/examples.css"
56 | "site/public/css/main.css"]}))
57 |
--------------------------------------------------------------------------------
/demo/reagentdemo/dev.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.dev
2 | "Initializes the demo app, and runs the tests."
3 | (:require [reagentdemo.core :as core]
4 | [reagenttest.runtests :as tests]))
5 |
6 | (enable-console-print!)
7 |
8 | (defn ^:dev/after-load init! []
9 | (core/init! (tests/init!)))
10 |
11 | (init!)
12 |
--------------------------------------------------------------------------------
/demo/reagentdemo/news.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.news
2 | (:require [reagentdemo.news.anyargs :as anyargs]
3 | [reagentdemo.news.async :as async]
4 | [reagentdemo.news.undodemo :as undodemo]
5 | [reagentdemo.news.clockpost :as clock]
6 | [reagentdemo.news.news050 :as news050]
7 | [reagentdemo.news.news051 :as news051]
8 | [reagentdemo.news.news060 :as news060]
9 | [reagentdemo.news.news061 :as news061]
10 | [reagentdemo.news.news060rc :as news060rc]
11 | [reagentdemo.news.news060release :as news060r]
12 | [sitetools.core :as tools]))
13 |
14 | (defn main []
15 | [:div
16 | [:div.reagent-demo
17 | [:h1 [:a {:href "https://github.com/reagent-project/reagent/blob/master/CHANGELOG.md"} "Check changelog for the latest releases"]]]
18 |
19 | [:div.reagent-demo
20 | [:h1 [:a {:href "https://github.com/reagent-project/reagent/blob/master/CHANGELOG.md"} "Reagent 1.0.0-alpha"]]]
21 |
22 | [:div.reagent-demo
23 | [:h1 [:a {:href "https://github.com/reagent-project/reagent/blob/master/CHANGELOG.md#0100-2020-03-06"} "Reagent 0.10.0"]]
24 | [:span "2020-03-06"]]
25 |
26 | [:div.reagent-demo
27 | [:h1 [:a {:href "https://github.com/reagent-project/reagent/blob/master/CHANGELOG.md#091-2020-01-15"} "Reagent 0.9.1"]]
28 | [:span "2020-01-15"]]
29 |
30 | [:div.reagent-demo
31 | [:h1 [:a {:href "https://github.com/reagent-project/reagent/blob/master/CHANGELOG.md#081-2018-05-15"} "Reagent 0.8.1"]]
32 | [:span "2018-05-15"]]
33 |
34 | [:div.reagent-demo
35 | [:h1 [:a {:href "https://github.com/reagent-project/reagent/blob/master/CHANGELOG.md#080-2018-04-19"} "Reagent 0.8.0"]]
36 | [:span "2018-04-19"]]
37 |
38 | [:div.reagent-demo
39 | [:h1 [:a {:href "https://github.com/reagent-project/reagent/blob/master/CHANGELOG.md#070-2017-06-27"} "Reagent 0.7.0"]]
40 | [:span "2017-06-27"]]
41 |
42 | [:div.reagent-demo
43 | [:h1 [:a {:href "https://github.com/reagent-project/reagent/blob/master/CHANGELOG.md#062-2017-05-19"} "Reagent 0.6.2"]]
44 | [:span "2017-05-19"]]
45 |
46 | [news061/main {:summary true}]
47 | [news060r/main {:summary true}]
48 | [news060rc/main {:summary true}]
49 | [news060/main {:summary true}]
50 | [news051/main {:summary true}]
51 | [news050/main {:summary true}]
52 | [clock/main {:summary true}]
53 | [anyargs/main {:summary true}]
54 | [async/main {:summary true}]
55 | [undodemo/main {:summary true}]
56 | [:div.reagent-demo
57 | [:h1 "Reagent 0.1.0"]
58 | [:span "2014-01-10"]]
59 | [:div.reagent-demo
60 | [:h1 "Reagent 0.0.2"]
61 | [:span "2013-12-17"]]])
62 |
63 | (def url "/news/index.html")
64 | (tools/register-page url [#'main] "News")
65 |
--------------------------------------------------------------------------------
/demo/reagentdemo/news/anyargs.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.news.anyargs
2 | (:require [reagent.core :as r]
3 | [reagentdemo.syntax :as s]
4 | [sitetools.core :as tools :refer [link]]
5 | [reagentdemo.common :as common :refer [demo-component]]
6 | [geometry.core :as geometry]))
7 |
8 | (def url "/news/any-arguments.html")
9 | (def title "All arguments allowed")
10 |
11 | (def ns-src (s/syntaxed "(ns example
12 | (:require [reagent.core :as r]))"))
13 |
14 | (defn hello-component [name]
15 | [:p "Hello, " name "!"])
16 |
17 | (defn say-hello []
18 | [hello-component "world"])
19 |
20 | (defn geometry-example []
21 | [geometry/main {:width "100%" :height 500}])
22 |
23 | (defn my-div []
24 | (let [this (r/current-component)]
25 | (into [:div.custom (r/props this)]
26 | (r/children this))))
27 |
28 | (defn call-my-div []
29 | [:div
30 | [my-div "Some text."]
31 | [my-div {:style {:font-weight 'bold}}
32 | [:p "Some other text in bold."]]])
33 |
34 | (defn main [{:keys [summary]}]
35 | (let [geometry {:href "https://github.com/reagent-project/reagent/tree/master/examples/geometry"}
36 | jonase {:href "https://github.com/jonase"}]
37 | [:div.reagent-demo
38 | [:h1 [link {:href url} title]]
39 | [:span "2014-02-15"]
40 |
41 | [:div.demo-text
42 |
43 | [:h2 "If it looks like a function…"]
44 |
45 | [:p "Calling a component in Reagent looks a lot like a function
46 | call. Now it also " [:em "works"] " like one."]
47 |
48 | [:p "Before 0.4.0, component functions were always called with
49 | three arguments: a map of attributes, a vector of ”children”,
50 | and the current React component."]
51 |
52 | [:p "This was confusing, and an unnecessary limitation, so now
53 | component functions get exactly the same arguments you pass to
54 | them."]
55 |
56 | (if summary
57 | [link {:href url :class 'news-read-more} "Read more"]
58 | [:div.demo-text
59 | [:p "In other words, you can now do this:"]
60 |
61 | [demo-component {:comp say-hello
62 | :src (s/src-of [:hello-component :say-hello])}]
63 |
64 | [:p "In the above example, it wouldn’t make any difference at
65 | all if " [:code "hello-component"] " had been called as a
66 | function, i.e with parentheses instead of brackets (except
67 | for performance, since components are cached between renders
68 | if the arguments to them don’t change)."]
69 |
70 | [:p "But there is one drawback: component function no longer
71 | receives the ”current component” as a parameter. Instead
72 | you’ll have to use "
73 | [:code "reagent.core/current-component"]
74 | " in order to get that. Beware that "
75 | [:code "current-component"] " is only valid in component
76 | functions, and must be called outside of e.g event handlers
77 | and " [:code "for"] " expressions, so it’s safest to always
78 | put the call at the top, as in " [:code "my-div"] " here:"]
79 |
80 | [demo-component {:comp call-my-div
81 | :src [:pre
82 | ns-src
83 | (s/src-of [:my-div :call-my-div])]}]
84 |
85 | [:p [:em "Note: "] [:code "r/props"] " and "
86 | [:code "r/children"] " correspond to React’s "
87 | [:code "this.props"] " and " [:code "this.props.children"] ",
88 | respectively. They may be convenient to use when wrapping
89 | native React components, since they follow the same
90 | conventions when interpreting the arguments given."]
91 |
92 | [:h2 "Other news in 0.4.0"]
93 |
94 | [:ul
95 |
96 | [:li "React has been updated to version 0.9.0."]
97 |
98 | [:li "You can now use any object that satisfies "
99 | [:code "ifn?"] " as a component function, and not just
100 | plain functions. That includes functions defined with "
101 | [:code "deftype"] ", " [:code "defrecord"] ", etc, as well
102 | as collections like maps."]
103 |
104 | [:li
105 | [:code "reagent.core/set-state"] " and "
106 | [:code "reagent.core/replace-state"] " are now implemented
107 | using an " [:code "reagent.core/atom"] ", and are
108 | consequently async."]
109 |
110 | [:li "Keys associated with items in a seq (e.g ”dynamic
111 | children” in React parlance) can now be specified with
112 | meta-data, as well as with a " [:code ":key"] " item in the
113 | first parameter as before. In other words, these two forms
114 | are now equivalent: " [:code "^{:key foo} [:li bar]"] "
115 | and " [:code "[:li {:key foo} bar]"] "."]
116 |
117 | [:li "Performance has been improved even further. For
118 | example, there is now practically no overhead for
119 | tracking derefs in components that don’t use "
120 | [:code "atom"] "s. Allocations and memory use have also
121 | been reduced."]
122 |
123 | [:li "Intro and examples have been tweaked a little to take
124 | advantage of the new calling conventions."]]
125 |
126 | [:h2 "New svg example"]
127 |
128 | [:p "There is also a new, elegant and simple "
129 | [:a geometry "example"] " of using svg with Reagent, written
130 | by " [:a jonase "Jonas Enlund"] ". It also shows how you can
131 | use Reagent’s new calling convensions, and looks like
132 | this:"]
133 |
134 | [demo-component {:comp geometry-example}]])]]))
135 |
136 | (tools/register-page url [#'main] title)
137 |
--------------------------------------------------------------------------------
/demo/reagentdemo/news/binaryclock.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.news.binaryclock
2 | (:require [reagent.core :as r]))
3 |
4 | (defn cell [n bit]
5 | [:div.clock-cell {:class (if (bit-test n bit)
6 | "light"
7 | "dark")}])
8 |
9 | (defn column [n]
10 | [:div.clock-col
11 | [cell n 3]
12 | [cell n 2]
13 | [cell n 1]
14 | [cell n 0]
15 | [:div.clock-cell n]])
16 |
17 | (defn column-pair [n]
18 | [:div.clock-pair
19 | [column (quot n 10)]
20 | [column (mod n 10)]])
21 |
22 | (defn legend [& items]
23 | (into [:div.clock-col.clock-legend]
24 | (map (partial vector :div.clock-cell)
25 | items)))
26 |
27 | (defn clock [date show-100s toggle-100s]
28 | [:div.clock-main {:on-click toggle-100s
29 | :class (when show-100s "wide")}
30 | [legend 8 4 2 1]
31 | [column-pair (.getHours date)]
32 | [column-pair (.getMinutes date)]
33 | [column-pair (.getSeconds date)]
34 | (when show-100s
35 | [column-pair (-> (.getMilliseconds date)
36 | (quot 10))])])
37 |
38 | (def clock-state (r/atom {:time (js/Date.)
39 | :show-100s false}))
40 |
41 | (defn update-time []
42 | (swap! clock-state assoc :time (js/Date.)))
43 |
44 | (defn main []
45 | (let [{:keys [time show-100s]} @clock-state]
46 | (if show-100s
47 | (r/next-tick update-time)
48 | (js/setTimeout update-time 1000))
49 | [clock time show-100s
50 | #(swap! clock-state update-in [:show-100s] not)]))
51 |
--------------------------------------------------------------------------------
/demo/reagentdemo/news/clockpost.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.news.clockpost
2 | (:require [reagentdemo.syntax :as s]
3 | [sitetools.core :as tools :refer [link]]
4 | [reagentdemo.common :as common :refer [demo-component]]
5 | [reagentdemo.news.binaryclock :as binaryclock]))
6 |
7 | (def url "/news/binary-clock.html")
8 | (def title "A binary clock")
9 |
10 | (defn fn-src [src]
11 | [demo-component {:src src :no-heading true}])
12 |
13 | (defn main [{:keys [summary]}]
14 | (let [lexclock {:href "http://www.lexicallyscoped.com/2014/01/23/clojurescript-react-om-binary-clock.html"}
15 | hopclock {:href "http://pmbauer.github.io/2014/01/27/hoplon-binary-clock/"}
16 | om {:href "https://github.com/swannodette/om"}
17 | hoplon {:href "http://hoplon.io"}
18 | clocksrc {:href "https://github.com/reagent-project/reagent/blob/master/demo/reagentdemo/news/binaryclock.cljs"}]
19 |
20 | [:div.reagent-demo
21 | [:h1 [link {:href url} title]]
22 | [:span "2014-02-26"]
23 | [:div.demo-text
24 |
25 | (when-not summary
26 | [:div
27 | [:div.clearfix
28 | [binaryclock/main]]
29 | [:div [:strong "Click to toggle 1/100th seconds."]]])
30 |
31 | [:p "Fredrik Dyrkell wrote a very nice " [:a lexclock "binary
32 | clock"] " using " [:a om "Om"] ". I thought I’d replicate that
33 | using Reagent for fun (another re-write, using "
34 | [:a hoplon "Hoplon"] ", can be seen " [:a hopclock "here"] ")."]
35 |
36 | [:p "So, without further ado, here is a binary clock using Reagent."]
37 |
38 | (if summary
39 | [link {:href url :class 'news-read-mode} "Read more"]
40 | [:div.demo-text
41 |
42 | [fn-src (s/syntaxed "(ns example
43 | (:require [reagent.core :as r]))")]
44 |
45 | [:p "We start with the basics: The clock is built out of
46 | cells, with a light colour if the bit the cell corresponds to
47 | is set."]
48 |
49 | [fn-src (s/src-of [:cell]
50 | "reagentdemo/news/binaryclock.cljs")]
51 |
52 | [:p "Cells are combined into columns of four bits, with a
53 | decimal digit at the bottom."]
54 |
55 | [fn-src (s/src-of [:column]
56 | "reagentdemo/news/binaryclock.cljs")]
57 |
58 | [:p "Columns are in turn combined into pairs:"]
59 |
60 | [fn-src (s/src-of [:column-pair]
61 | "reagentdemo/news/binaryclock.cljs")]
62 |
63 | [:p "We'll also need the legend on the left side:"]
64 |
65 | [fn-src (s/src-of [:legend]
66 | "reagentdemo/news/binaryclock.cljs")]
67 |
68 | [:p "We combine these element into a component that shows the
69 | legend, hours, minutes and seconds; and optionally 1/100
70 | seconds. It also responds to clicks."]
71 |
72 | [fn-src (s/src-of [:clock]
73 | "reagentdemo/news/binaryclock.cljs")]
74 |
75 | [:p "We also need to keep track of the time, and of the
76 | detail shown, in a Reagent atom. And a function to update the
77 | time."]
78 |
79 | [fn-src (s/src-of [:clock-state :update-time]
80 | "reagentdemo/news/binaryclock.cljs")]
81 |
82 | [:p "And finally we use the " [:code "clock"] " component.
83 | The current time is scheduled to be updated, after a suitable
84 | delay, every time the main component is rendered ("
85 | [:code "reagent.core/next-tick"] " is just a front for "
86 | [:code "requestAnimationFrame"] "):"]
87 |
88 | [fn-src (s/src-of [:main]
89 | "reagentdemo/news/binaryclock.cljs")]
90 |
91 | [:p "The entire source is also available "
92 | [:a clocksrc "here"] "."]
93 |
94 | [:h2 "How it all works"]
95 |
96 | [:p "Reading through the source, it may look like the entire
97 | clock component is recreated from scratch whenever the time
98 | changes. "]
99 |
100 | [:p "That is an illusion: Reagent and React together
101 | makes sure that only the parts of the DOM that actually need
102 | to change are updated. For example, the "
103 | [:code "column-pair"] " function corresponding to hours only
104 | runs once every hour."]
105 |
106 | [:p "And that’s what makes Reagent and React fast. Try
107 | clicking on the clock to toggle the display of 1/100th
108 | seconds. Most browsers should have no trouble at all keeping
109 | up (even if they won’t actually show every 1/100th second:
110 | they are typically limited to roughly 60 fps)."]
111 |
112 | [:p "But it is a very handy illusion. Almost the entire UI is
113 | made up of pure functions, transforming immutable data into
114 | other immutable data structures. That makes them easy to
115 | reason about, and trivial to test. You don’t have to care
116 | about ”model objects”, or about how to update the DOM
117 | efficiently. "]
118 |
119 | [:p "Just pass arguments to component functions, return a UI
120 | description that corresponds to those arguments, and leave it
121 | to React to actually display that UI."]])]]))
122 |
123 | (tools/register-page url [#'main] title)
124 |
--------------------------------------------------------------------------------
/demo/reagentdemo/news/news051.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.news.news051
2 | (:require [reagent.core :as r]
3 | [reagentdemo.syntax :as s]
4 | [sitetools.core :as tools :refer [link]]
5 | [reagentdemo.common :as common :refer [demo-component]]
6 | [clojure.string :as string]))
7 |
8 | (def url "/news/news051.html")
9 | (def title "News in 0.5.1")
10 |
11 | (def ns-src (s/syntaxed "(ns example.core
12 | (:require [reagent.core :as r]))"))
13 |
14 | (defn old-and-tired []
15 | [:ul
16 | [:li.foo [:a.bar "Text 1"]]
17 | [:li.foo [:a.bar "Text 2"]]])
18 |
19 | (defn new-hotness []
20 | [:ul
21 | [:li.foo>a.bar "Text 1"]
22 | [:li.foo>a.bar "Text 2"]])
23 |
24 | (def upper-value (r/atom "FOOBAR"))
25 |
26 | (defn upper-input []
27 | [:div
28 | [:p "Value is: " @upper-value]
29 | [:input {:type 'text :value @upper-value
30 | :on-change #(reset! upper-value
31 | (-> % .-target .-value string/upper-case))}]])
32 |
33 |
34 | (defn main [{:keys [summary]}]
35 | [:div.reagent-demo
36 | [:h1 [link {:href url} title]]
37 | [:span "2015-09-09"]
38 | [:div.demo-text
39 | [:p "Reagent 0.5.1 contains a new convenient shortcut for nested
40 | elements, better error messages, new logic for maintaining cursor
41 | position in inputs, a new version of React, and some bug fixes and
42 | improvements."]
43 |
44 | (if summary
45 | [link {:href url :class 'news-read-more} "Read more"]
46 | [:div.demo-text
47 | [:h2 "New syntax for nested elements"]
48 |
49 | [:p "The ”Hiccup syntax” used in Reagent now supports defining
50 | nested elements using ”>” as a separator in keywords. This is
51 | probably easier to show than to explain…"]
52 |
53 | [:p "So, instead of doing this: "]
54 |
55 | [demo-component {:comp old-and-tired
56 | :src (s/src-of [:old-and-tired])}]
57 |
58 | [:p "you can now do this: "]
59 |
60 | [demo-component {:comp new-hotness
61 | :src (s/src-of [:new-hotness])}]
62 |
63 | [:p "with identical results, thus saving several square
64 | brackets from an untimely death."]
65 |
66 |
67 | [:h2 "Keeping position"]
68 |
69 | [:p "Reagent now tries harder to maintain cursor position in
70 | text inputs, even when the value of the input is transformed in
71 | code."]
72 |
73 | [:p "Previously, the cursor would jump to the end of the text
74 | whenever you made a change in the middle of the text in
75 | something like this:"]
76 |
77 | [demo-component {:comp upper-input
78 | :src [:pre ns-src
79 | (s/src-of [:upper-value :upper-input])]}]
80 |
81 |
82 | [:h2 "Other news"]
83 |
84 | [:ul
85 | [:li "React is updated to 0.13.3."]
86 |
87 | [:li "A bit better error messages. In particular, the current
88 | component path is now printed when an exception is thrown."]
89 |
90 | [:li "There is a new
91 | function, " [:code "reagent.core/force-update"] " that will
92 | make a component render immediately. It takes an optional
93 | second parameter that forces re-rendering of every child
94 | component as well."]
95 |
96 | [:li "Calling the result
97 | of " [:code "reagent.core/create-class"] " as a function is
98 | now deprecated. Use Hiccup syntax instead."]
99 |
100 | [:li "Some other bug fixes and performance tweaks."]]])]])
101 |
102 | (tools/register-page url [#'main] title)
103 |
--------------------------------------------------------------------------------
/demo/reagentdemo/news/news060rc.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.news.news060rc
2 | (:require [reagent.core :as r]
3 | [reagentdemo.syntax :as s]
4 | [sitetools.core :as tools :refer [link]]
5 | [reagentdemo.news.news060 :as news060]
6 | [reagentdemo.common :as common :refer [demo-component]]))
7 |
8 | (def url "/news/news060-rc.html")
9 | (def title "Reagent 0.6.0-rc")
10 |
11 | (def ns-src (s/syntaxed "(ns example.core
12 | (:require [reagent.core :as r]))"))
13 |
14 | (defn mixed []
15 | [:div
16 | "Symbols are " 'ok " as well as " :keywords "."])
17 |
18 | (def some-atom (r/atom 0))
19 |
20 | (defn confusion-avoided []
21 | [:div "This is some atom: " some-atom])
22 |
23 |
24 | (defn main [{:keys [summary]}]
25 | [:div.reagent-demo
26 | [:h1 [link {:href url} title]]
27 | [:span "2016-09-14"]
28 | [:div.demo-text
29 | [:p "Reagent 0.6.0-rc has been given a lot of testing, a new
30 | version of React (15.1.0), bug fixing and some small general
31 | improvements since 0.6.0-alpha. It has one new feature: general
32 | ClojureScript objects can now be used anywhere in markup
33 | content."]
34 |
35 | (if summary
36 | [link {:href url :class 'news-read-more} "Read more"]
37 | [:div.demo-text
38 | [:section.demo-text
39 | [:p "See " [link {:href news060/url} "this
40 | article"] " for more information about Reagent 0.6.0."]
41 |
42 | [:h2 "Generalized markup"]
43 |
44 | [:p "Symbols and keywords can now be used in markup content
45 | like this: "]
46 |
47 | [demo-component {:comp mixed
48 | :src (s/src-of [:mixed])}]
49 |
50 | [:p "This makes content conversions behave the same as in
51 | attributes, where symbols and keywords have been supported
52 | before. "]
53 |
54 | [:p "But mainly it avoids confusing error messages when you
55 | happen to drop an arbitrary ClojureScript object into the
56 | markup, like this: "]
57 |
58 | [demo-component {:comp confusion-avoided
59 | :src (s/src-of [:some-atom
60 | :confusion-avoided])}]
61 |
62 | [:p "This may not be particularly useful, but it is at least a
63 | lot better than getting a quite confusing error message from
64 | React, that no longer accepts unknown objects…"]
65 |
66 | [:p "Any object hat satisfies IPrintWithWriter is allowed, and
67 | is converted to a string using " [:code "pr-str" "."]]]])]])
68 |
69 |
70 | (tools/register-page url [#'main] title)
71 |
--------------------------------------------------------------------------------
/demo/reagentdemo/news/news060release.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.news.news060release
2 | (:require [reagentdemo.syntax :as s]
3 | [sitetools.core :as tools :refer [link]]
4 | [reagentdemo.news.news060 :as news060]
5 | [reagentdemo.news.news060rc :as news060rc]))
6 |
7 | (def url "/news/news060.html")
8 | (def title "Reagent 0.6.0")
9 |
10 | (def ns-src (s/syntaxed "(ns example.core
11 | (:require [reagent.core :as r]))"))
12 |
13 | (def changelog
14 | "https://github.com/reagent-project/reagent/blob/master/CHANGELOG.md")
15 |
16 | (defn abstract []
17 | [:div.demo-text
18 | [:p
19 | "Reagent 0.6.0 has a new version of React (15.2.1), and a few
20 | bug fixes. Otherwise it is identical to 0.6.0-rc."]])
21 |
22 | (defn story []
23 | [:div.demo-text
24 | [:p
25 | "See " [link {:href news060/url} "this story"]
26 | " for much more information about Reagent 0.6.0."]
27 | [:p
28 | "You can also have a look at the "
29 | [link {:href news060rc/url} "news in 0.6.0-rc"]
30 | " and the " [link {:href changelog} "change log"]
31 | "."]])
32 |
33 | (defn main [{:keys [summary]}]
34 | [:div.reagent-demo
35 | [:h1
36 | [link {:href url} title]]
37 | [:span "2016-06-09"]
38 | [:div
39 | [abstract]
40 | (if summary
41 | [link {:href url :class 'news-read-more} "Read more"]
42 | [:section.demo-text
43 | [story]])]])
44 |
45 | (tools/register-page url [#'main] title)
46 |
--------------------------------------------------------------------------------
/demo/reagentdemo/news/news061.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.news.news061
2 | (:require [reagentdemo.syntax :as s]
3 | [sitetools.core :as tools :refer [link]]))
4 |
5 | (def url "/news/news061.html")
6 | (def title "Reagent 0.6.1")
7 |
8 | (def ns-src (s/syntaxed "(ns example.core
9 | (:require [reagent.core :as r]))"))
10 |
11 | (def changelog
12 | "https://github.com/reagent-project/reagent/blob/master/CHANGELOG.md")
13 |
14 | (defn abstract []
15 | [:div.demo-text
16 | [:p
17 | "Reagent 0.6.1 has a new version of React (15.4.0), and it fixes a bug with
18 | " [:code ":ref"] " attributes on " [:code "input"] " elements: " [:a {:href "https://github.com/reagent-project/reagent/issues/259"} "#259"] "."]])
19 |
20 | (defn main [{:keys [summary]}]
21 | [:div.reagent-demo
22 | [:h1
23 | [link {:href url} title]]
24 | [:span "2017-03-11"]
25 | [:div
26 | [abstract]]])
27 |
28 | (tools/register-page url [#'main] title)
29 |
--------------------------------------------------------------------------------
/demo/reagentdemo/news/undodemo.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.news.undodemo
2 | (:require [reagent.core :as r]
3 | [reagentdemo.syntax :as s]
4 | [sitetools.core :as tools :refer [link]]
5 | [reagentdemo.common :as common :refer [demo-component]]
6 | [todomvc.core :as todomvc]))
7 |
8 | (def url "/news/cloact-reagent-undo-demo.html")
9 | (def title "Cloact becomes Reagent: Undo is trivial")
10 |
11 | (def ns-src (s/syntaxed "(ns example
12 | (:require [reagent.core :as r]))"))
13 |
14 | (def state todomvc/todos)
15 |
16 | (def undo-list (r/atom nil))
17 |
18 | (defn undo []
19 | (let [undos @undo-list]
20 | (when-let [old (first undos)]
21 | (reset! state old)
22 | (reset! undo-list (rest undos)))))
23 |
24 | (defn undo-button []
25 | (let [n (count @undo-list)]
26 | [:input {:type "button" :on-click undo
27 | :disabled (zero? n)
28 | :value (str "Undo (" n ")")}]))
29 |
30 | (defn todomvc-with-undo []
31 | (add-watch state ::undo-watcher
32 | (fn [_ _ old-state _]
33 | (swap! undo-list conj old-state)))
34 | [:div
35 | [undo-button]
36 | [todomvc/todo-app]])
37 |
38 | (defn undo-demo []
39 | [demo-component {:comp todomvc-with-undo
40 | :src [:pre ns-src
41 | (s/src-of [:state :undo-list :undo :save-state
42 | :undo-button :todomvc-with-undo])]}])
43 |
44 | (def undo-demo-cleanup
45 | (with-meta undo-demo {:component-will-unmount
46 | (fn []
47 | (reset! undo-list nil)
48 | (remove-watch state ::undo-watcher))}))
49 |
50 | (defn main [{:keys [summary]}]
51 | (let [head title]
52 | [:div.reagent-demo
53 | [:h1 [link {:href url} head]]
54 | ;; Undo demo originall added on 0a0dbdee7dfdaa545171d41fa1c3a18a1cae6e1b
55 | [:span "2014-01-19"]
56 |
57 | [:div.demo-text
58 | [:h2 "(reset! cloact-name \"Reagent\")"]
59 |
60 | [:p "It turns out that ”Cloact” was a really, really bad
61 | name. It made some people think about birds’ behinds, in
62 | possibly unhealthy ways, which even Google suggested they
63 | should."]
64 |
65 | [:p "The new name is " [:strong "Reagent"] ", which hopefully
66 | doesn’t bring with it the same disgusting connotations."]
67 |
68 | [:p "The API is otherwise unchanged, so a simple
69 | search-and-replace should suffice."]
70 |
71 | (if summary
72 | [link {:href url
73 | :class 'news-read-more} "Read more"]
74 | [:div.demo-text
75 |
76 | [:h2 "Undo the easy way"]
77 |
78 | [:p "To celebrate the undoing of the apparently disgusting
79 | name, here is an example of how easy it is to add undo
80 | functionality to Reagent components."]
81 |
82 | [:p "It simply saves the old state whenever it changes, and
83 | restores it when the button is clicked."]
84 |
85 | [:p "The really nice thing about ClojureScript is that not
86 | only is this easy and safe to do, courtesy of immutable data
87 | structures, it is also efficient. ClojureScript figures out
88 | how to represent ”changes” to maps and vectors efficiently,
89 | so that you won’t have to."]
90 |
91 | [undo-demo-cleanup]])]]))
92 |
93 | (tools/register-page url [#'main] title)
94 |
--------------------------------------------------------------------------------
/demo/reagentdemo/prod.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.prod
2 | "Initializes the demo app"
3 | (:require [reagentdemo.core :as core]))
4 |
5 | (core/init!)
6 |
7 |
--------------------------------------------------------------------------------
/demo/reagentdemo/syntax.clj:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.syntax
2 | (:require [clojure.java.io :as io]
3 | [clojure.string :as string]
4 | [cljs.analyzer :as analyzer]))
5 |
6 | ;;; Source splitting
7 |
8 | (defn src-parts [src]
9 | (string/split src #"\n(?=[(])"))
10 |
11 | (defn src-defs [parts]
12 | (let [ws #"[^ \t\n]+"]
13 | (into {} (for [x parts]
14 | [(->> x (re-seq ws) second keyword) x]))))
15 |
16 | (defn fun-map [src]
17 | (-> src src-parts src-defs))
18 |
19 | (defn src-for-names [srcmap names]
20 | (string/join "\n" (map srcmap names)))
21 |
22 |
23 | ;;; Macros
24 |
25 | (defmacro syntaxed [src]
26 | `(reagentdemo.syntax/syntaxify ~src))
27 |
28 | ;; ;; A much simpler way to find source: currently broken with #js annotations
29 | ;; (defmacro src-for [& syms]
30 | ;; (let [s (map #(list 'with-out-str (list 'cljs.repl/source %)) syms)]
31 | ;; `(->> [~@s]
32 | ;; (string/join "\n")
33 | ;; syntaxed)))
34 |
35 | ;; (defmacro src-from-file [f]
36 | ;; (let [src (-> f io/resource slurp)]
37 | ;; `(syntaxed ~src)))
38 |
39 | (defmacro src-of
40 | ([funs]
41 | `(src-of ~funs nil))
42 | ([funs resource]
43 | (assert (or (nil? funs)
44 | (vector? funs)))
45 | (assert (or (nil? resource)
46 | (string? resource)))
47 | (let [f (if (nil? resource)
48 | (-> (name analyzer/*cljs-ns*)
49 | (string/replace #"[.]" "/")
50 | (str ".cljs"))
51 | resource)
52 | src (-> f io/resource slurp)
53 | sel (if (nil? funs)
54 | src
55 | (-> src fun-map (src-for-names funs)))]
56 | `(syntaxed ~sel))))
57 |
--------------------------------------------------------------------------------
/demo/reagentdemo/syntax.cljs:
--------------------------------------------------------------------------------
1 | (ns reagentdemo.syntax
2 | (:require-macros reagentdemo.syntax)
3 | (:require [clojure.string :as string]))
4 |
5 | ;; Styles for syntax highlighting
6 |
7 | (def comment-style {:style {:color "gray" :font-style "italic"}})
8 | (def string-style {:style {:color "green"}})
9 | (def keyword-style {:style {:color "blue"}})
10 | (def builtin-style {:style {:color "#687868" :font-weight "bold"}})
11 | (def def-style {:style {:color "#5050c0" :font-weight "bold"}})
12 |
13 | (def paren-style-1 {:style {:color "#272"}})
14 | (def paren-style-2 {:style {:color "#940"}})
15 | (def paren-style-3 {:style {:color "#44a"}})
16 |
17 |
18 | ;;;;; Colorization
19 |
20 | (def builtins #{"def" "defn" "defonce" "ns" "atom" "let" "if" "when"
21 | "cond" "merge" "assoc" "swap!" "reset!" "for"
22 | "range" "nil?" "int" "or" "->" "->>" "%" "fn" "if-not"
23 | "empty?" "case" "str" "pos?" "zero?" "map" "remove"
24 | "empty" "into" "assoc-in" "dissoc" "get-in" "when-not"
25 | "filter" "vals" "count" "complement" "identity" "dotimes"
26 | "update-in" "sorted-map" "inc" "dec" "false" "true" "not"
27 | "=" "partial" "first" "second" "rest" "list" "conj"
28 | "drop" "when-let" "if-let" "add-watch" "mod" "quot"
29 | "bit-test" "vector" "do" "try" "catch" "finally"})
30 |
31 | (def styles {:comment comment-style
32 | :str-litt string-style
33 | :keyw keyword-style
34 | :builtin builtin-style
35 | :def def-style})
36 |
37 | (def paren-styles [paren-style-1 paren-style-2 paren-style-3])
38 |
39 | (def tokenize-pattern
40 | (let [ws " \\t\\n"
41 | open "\\[({"
42 | close ")\\]}"
43 | sep (str ws open close)
44 | comment-p ";.*"
45 | str-p "\"[^\"]*\""
46 | open-p (str "[" open "]")
47 | close-p (str "[" close "]")
48 | iden-p (str "[^" sep "]+")
49 | meta-p (str "\\^" iden-p)
50 | any-p (str "[" ws "]+|\\^[^" sep "]+|.")]
51 | (re-pattern (str "("
52 | (string/join ")|(" [comment-p str-p open-p
53 | close-p meta-p iden-p any-p])
54 | ")"))))
55 |
56 | (def keyw-re #"^:")
57 | (def qualif-re #"^[a-z]+/")
58 | (def def-re #"^def|^ns\b")
59 |
60 | (defn tokenize [src]
61 | (for [[s comment strlitt open close met iden any]
62 | (re-seq tokenize-pattern src)]
63 | (cond
64 | (some? comment) [:comment s]
65 | (some? strlitt) [:str-litt s]
66 | (some? open) [:open s]
67 | (some? close) [:close s]
68 | (some? met) [:other s]
69 | (some? iden) (cond (re-find keyw-re s) [:keyw s]
70 | (builtins s) [:builtin s]
71 | (re-find qualif-re s) [:builtin s]
72 | :else [:iden s])
73 | (some? any) [:other s])))
74 |
75 | (defn syntaxify [src]
76 | (let [ncol (count paren-styles)
77 | paren-style #(nth paren-styles (mod % ncol))]
78 | (loop [tokens (tokenize (str src " "))
79 | prev nil
80 | level 0
81 | res []]
82 | (let [[kind val] (first tokens)
83 | level' (case kind
84 | :open (inc level)
85 | :close (dec level)
86 | level)
87 | style (case kind
88 | :iden (when (and prev (re-find def-re prev))
89 | (:def styles))
90 | :open (paren-style level)
91 | :close (paren-style level')
92 | (styles kind))
93 | remain (rest tokens)]
94 | (if-not (empty? remain)
95 | (recur remain
96 | (case kind :other prev val)
97 | level'
98 | (if (nil? style)
99 | (let [old (peek res)]
100 | (if (string? old)
101 | (conj (pop res) (str old val))
102 | (conj res val)))
103 | (conj res [:span style val])))
104 | (into [:pre] res))))))
105 |
--------------------------------------------------------------------------------
/demo/sitetools/core.cljs:
--------------------------------------------------------------------------------
1 | (ns sitetools.core
2 | (:require [clojure.string :as string]
3 | [goog.events :as evt]
4 | ["react" :as react]
5 | [reagent.core :as r]
6 | [reagent.dom :as rdom]
7 | [reagent.dom.client :as rdomc])
8 | (:import goog.History
9 | [goog.history Html5History EventType]))
10 |
11 | ;;; Configuration
12 |
13 | (declare main-content)
14 |
15 | (defonce root
16 | ;; Init only on use, this ns is loaded for SSR build also
17 | (delay (rdomc/create-root (js/document.getElementById "main-content"))))
18 |
19 | (defonce config (r/atom {:body [#'main-content]
20 | :pages {"/index.html" {:content [:div]
21 | :title ""}}
22 | :site-dir "target/prerender/public/"
23 | :css-infiles ["site/public/css/main.css"]
24 | :css-file "css/built.css"
25 | :js-file "js/main.js"
26 | :react-root root
27 | :default-title ""}))
28 |
29 | (defonce history nil)
30 |
31 | (defn demo-handler [state [id x :as event]]
32 | (case id
33 | :set-content (let [page x
34 | title (:title page)
35 | title (if title
36 | (str (:title-prefix state) title)
37 | (str (:default-title state)))]
38 | (when r/is-client
39 | (set! js/document.title title))
40 | (assoc state :current-page page :title title))
41 | :set-page (let [path x
42 | _ (assert (string? path))
43 | ps (:pages state)
44 | p (get ps path (get ps "/index.html"))]
45 | (recur state [:set-content p]))
46 | :goto-page (let [path x
47 | _ (assert (string? path))]
48 | (when-some [h history]
49 | (r/after-render (fn []
50 | (.setToken h x)
51 | (js/scrollTo 0 0)))
52 | state)
53 | (recur state [:set-page x]))))
54 |
55 | (defn emit [event]
56 | ;; (dbg event)
57 | (r/rswap! config demo-handler event))
58 |
59 | (defn register-page [url comp title]
60 | {:pre [(re-matches #"/.*[.]html" url)
61 | (vector? comp)]}
62 | (swap! config update-in [:pages]
63 | assoc url {:content comp :title title}))
64 |
65 |
66 | ;;; History
67 |
68 | (defn init-history [page]
69 | (when-not history
70 | (let [html5 (and page
71 | (Html5History.isSupported)
72 | (#{"http:" "https:"} js/location.protocol))
73 | h (if html5
74 | (doto (Html5History.)
75 | (.setUseFragment false)
76 | (.setPathPrefix (-> js/location.pathname
77 | (string/replace
78 | (re-pattern (str page "$")) "")
79 | (string/replace #"/*$" ""))))
80 | (History.))]
81 | (set! history h)
82 | (evt/listen history EventType.NAVIGATE
83 | (fn [^js/Event e]
84 | (when (.-isNavigation e)
85 | (emit [:set-page (.-token e)])
86 | (r/flush))))
87 | (.setEnabled history true)
88 | (let [token (.getToken history)
89 | p (if (and page (not html5) (empty? token))
90 | page
91 | token)]
92 | (emit [:set-page p])))))
93 |
94 | (defn to-relative [f]
95 | (string/replace f #"^/" ""))
96 |
97 |
98 | ;;; Components
99 |
100 | (defn link [props child]
101 | [:a (assoc props
102 | :href (-> props :href to-relative)
103 | :on-click #(do (.preventDefault %)
104 | (emit [:goto-page (:href props)])))
105 | child])
106 |
107 | (defn main-content []
108 | (get-in @config [:current-page :content]))
109 |
110 | ;;; Main entry points
111 |
112 | (defn start! [site-config]
113 | (swap! config merge site-config)
114 | (when r/is-client
115 | (let [page-conf (when (exists? js/pageConfig)
116 | (js->clj js/pageConfig :keywordize-keys true))
117 | conf (swap! config merge page-conf)
118 | {:keys [page-path body react-root]} conf]
119 | (init-history page-path)
120 | ;; Enable StrictMode to warn about e.g. findDOMNode
121 | (rdomc/render @react-root [:> react/StrictMode {} body]))))
122 |
--------------------------------------------------------------------------------
/deps.edn:
--------------------------------------------------------------------------------
1 | {:paths ["src" "test" "examples/todomvc/src" "examples/simple/src" "examples/geometry/src" "demo"]
2 | :deps {org.clojure/clojurescript {:mvn/version "1.11.132"}
3 | doo/doo {:mvn/version "0.1.11"}
4 | funcool/promesa {:mvn/version "11.0.678"}}}
5 |
--------------------------------------------------------------------------------
/doc/0.8-upgrade.md:
--------------------------------------------------------------------------------
1 | # 0.8 Upgrade guide
2 |
3 | The necessary changes depend on what environment you target, and
4 | how you want to provide React.
5 |
6 | | | Build | Browser | Node |
7 | |---|---|---|---|
8 | | Cljsjs | `:none` | Supported | Requires Cljs 1.10.238+ |
9 | | Cljsjs | `:advanced` | Supported | Requires Cljs 1.10.238+ |
10 | | `node modules` | `:none` | Requires Cljs 1.10.312 | Supported |
11 | | `node modules` | `:advanced` | Requires Cljs 1.10.312 | Supported |
12 |
13 | ## Browser - Cljsjs
14 |
15 | **This is the recommended setup.**
16 |
17 | Using Reagent with Cljsjs packages doesn't require changes,
18 | other than making sure you update Cljsjs React dependencies,
19 | if you have direct dependencies to them.
20 |
21 | To ensure Cljsjs libs are used instead of Node Modules, you can add `:npm-deps false`, which will make sure ClojureScript compiler doesn't look into `node_modules` directory if it exists.
22 |
23 | ## Browser - node modules
24 |
25 | If `react`, `react-dom` and `create-react-class` are available in `node_modules`
26 | directory, ClojureScript compiler will use these with Reagent.
27 |
28 | If you don't want to call `npm` and manage `package.json`, you can use `:npm-deps` and `:install-deps` compiler options to
29 | have ClojureScript install the packages automatically.
30 |
31 | You can use `:process-shim` compiler option to provide `process.env.NODE_ENV`
32 | constant which is used by JS code to enable development and production
33 | builds. ClojureScript compiler will automatically set this constant to
34 | `production` value when using `:advanced` optimizations. This enables
35 | the React production build.
36 |
37 | When using module processing, it should be possible to split output into several
38 | [modules](https://clojurescript.org/reference/compiler-options#modules).
39 |
40 | **Externs are required for use with node modules also!** React created objects
41 | statically in several places and then accesses those dynamically. Closure-compiler
42 | will in these cases rename the statically object properties, which will break
43 | dynamically accessing the objects. Externs fix this by defining which properties
44 | must not be renamed.
45 |
46 | ## Browser - Webpack
47 |
48 | https://clojurescript.org/guides/webpack
49 |
50 | If you want to load React.js yourself from external JS file (CDN) or from custom bundle,
51 | 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:
52 |
53 | ```clj
54 | :foreign-libs
55 | [{:file "bundje.js",
56 | :provides ["react" "react-dom" "create-react-class" "react-dom/server"],
57 | :global-exports {react React
58 | react-dom ReactDOM
59 | create-react-class createReactClass
60 | react-dom/server ReactDOMServer}}]
61 | ```
62 |
63 | ## NodeJS - Cljsjs
64 |
65 | Requires https://github.com/clojure/clojurescript/commit/f7d611d87f6ea8a605eae7c0339f30b79a840b49
66 |
67 | Available in 1.10.238
68 |
69 | Reagent should use Cljsjs libraries by default even when running on Node.
70 |
71 | ## NodeJS - node modules
72 |
73 | Install `react`, `react-dom` and `create-react-class` npm packages,
74 | and ClojureScript should automatically use `require` to
75 | load React for Reagent.
76 |
77 | ## Electron
78 |
79 | ???
80 |
81 | ## React-native
82 |
83 | https://github.com/drapanjanas/re-natal/issues/128
84 |
85 | ## Common Problems
86 |
87 | ### Mismatch with Cljsjs and npm packages
88 |
89 | If you have one npm package installed, e.g. `react`, you also need
90 | to provide others (`react-dom` and `create-react-class`), else
91 | Cljsjs packages would be used for these, and packages from different sources
92 | don't work together.
93 |
94 | ### Previous problems
95 |
96 | Before ClojureScript 1.10.312 there were couple of problems with npm support:
97 |
98 | 1. Closure can't properly handle React 16 CommonJS module pattern: https://github.com/google/closure-compiler/issues/2841
99 | This causes the production React code being loaded even for development builds.
100 | 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
101 | the included in next the ClojureScript release.
102 |
103 | 2. Closure optimization currently breaks certain statically created objects which are
104 | accessed dynamically in `ReactDOM/server`: https://github.com/facebook/react/issues/12368
105 | Fixed by using `[com.google.javascript/closure-compiler-unshaded "v20180319"]` ([fix commit](https://github.com/google/closure-compiler/commit/c13cf48b98477e44409dba6359246bffa95b1c7b)), will be
106 | the default in next ClojureScript release.
107 |
--------------------------------------------------------------------------------
/doc/ControlledInputs.md:
--------------------------------------------------------------------------------
1 | # Controlled inputs
2 |
3 | Reagent uses async rendering which causes problems with controlled inputs. If
4 | the input element is created directly by Reagent (i.e. `[:input ...]` in hiccup), [a
5 | workaround](https://github.com/reagent-project/reagent/blob/master/src/reagent/impl/input.cljs)
6 | can be applied, but if the input is created by JS library (i.e. JSX ``
7 | or React `create-element`), Reagent doesn't see
8 | the element so the workaround can't be applied.
9 |
10 | Due to async rendering, the DOM update doesn't occur during the event handler,
11 | but some time later. In certain cases, like when the cursor is not at the end
12 | of the input, updating the DOM input value causes the cursor to move to the
13 | end of the input. Without async rendering, browsers probably implement logic
14 | to keep the cursor position if the value is updated during event handler.
15 |
16 | Reagent workaround works by changing the React input element into
17 | uncontrolled input (i.e. the DOM value is not updated by React). Instead
18 | Reagent will update DOM itself if the Reagent input value property changes.
19 | This enables Reagent to check the cursor position before updating the
20 | value, and if needed, save and restore the cursor position
21 | after updating the value.
22 |
23 | For JS libraries, usually the best solution is if the library provides an option to
24 | use custom component to create the input element, which enables
25 | Reagent to create the input element:
26 |
27 | ## React-native
28 |
29 | ReactNative has it's own `TextInput` component. Similar workaround can't be (at least easily) implemented in ReactNative, as the component doesn't provide similar API as DOM Inputs to control the selection. Currently best option is to use uncontrolled inputs (`default-value` and `on-change`). If you also need to update the input value from your code, you could change to Input component React key to force recreation of the component:
30 |
31 | ```clj
32 | [:> TextInput
33 | {:key @k
34 | :default-value @v
35 | :on-change ...}]
36 |
37 | (reset! v "foo")
38 | (swap! k inc)
39 | ;; When key changes, old component is unmounted and new one created, and the new component will use the new default-value
40 | ```
41 |
42 | (Similar workaround can be also used with DOM inputs)
43 |
44 | ## Examples
45 |
46 | - [Material UI](./examples/material-ui.md)
47 | - [Smooth UI](./examples/smooth-ui.md)
48 |
--------------------------------------------------------------------------------
/doc/FAQ/CljsjsReactProblems.md:
--------------------------------------------------------------------------------
1 | # Question
2 |
3 | Reagent doesn't work after updating dependencies.
4 |
5 | # Answer
6 |
7 | If you see errors about accessing `React` or `ReactDOM` object or some React method after you have updated your dependencies, the problem is probably conflicting `cljsjs/react` or `cljsjs/react-dom` versions. Other dependencies than Reagent might bring in versions which don't work with Reagent, or a library which only depends on one of the packages might cause React and ReactDOM versions to conflict.
8 |
9 | To fix this you should check `lein deps :tree` or `boot show -d`, and check which version of Cljsjs React packages you have.
10 |
11 | There are three alternative solutions:
12 |
13 | 1. Update all the packages that require Cljsjs React packages to use same (or compatible) versions as Reagent
14 | 2. Add `:exclusion [cljsjs/react cljsjs/react-dom]` to problematic dependencies, so only Reagent
15 | will have transitive dependency on React packages
16 | 3. Add direct `cljsjs/react` and `cljsjs/react-dom` dependencies to your project, which will override any transitive dependencies
17 |
18 | Note: `cljsjs/react-dom-server` package is deprecated but Reagent still depends on empty package for compatibility.
19 |
20 | Note: For more information on how Leiningen and Boot resolve dependencies using Maven-resolver, read: https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html
21 |
--------------------------------------------------------------------------------
/doc/FAQ/ComponentNotRerendering.md:
--------------------------------------------------------------------------------
1 | ### Question
2 |
3 | My component is not re-rendering, what's wrong?
4 |
5 | ### Answer
6 |
7 | Ask yourself this question: why do you think the Component should have re-rendered? There's two possible answers:
8 | - a ratom (used by the Component) changed
9 | - the props for (arguments to) the Component changed
10 |
11 | We'll deal with these two cases seperately.
12 |
13 | ### A Ratom Changed
14 |
15 | If a ratom changes but your Component doesn't update, then the gotchas to look out for are:
16 | 1. Make sure you are using a `reagent.core/atom` (i.e. a Reagent ratom) instead of a normal `clojure.core/atom`. Carefully check the `require` at the top of the `ns`. Components are only reactive with respect to Reagent ratoms. They don't react to changes in a Clojure atom.
17 | 2. Make sure you actually `deref` your ratom (e.g. `@app-state`) during the render function of your component. It is a common mistake for people to forget the leading `@`. Note that derefs that happen outside of the render function (such as during event handlers) do not make your component reactive to that ratom.
18 | 3. Make sure your ratom will survive a rerender. Either declare it as a global var, or use a form-2 or form-3 component. [Read this](https://github.com/reagent-project/reagent-cookbook/tree/master/basics/component-level-state) if you want to understand why.
19 | 4. If you put your ratom in a form-2 or form-3 component, be sure you are calling that function using `[square brackets]`, not `(parenthesis)`.
20 | When function (component) is called using `( )` Reagent doesn't create new component, but instead just places the function's return value into current component. In this case the function closure which should hold the local state doesn't work.
21 | 5. Make sure to `deref` your ratom outside of a seq or wrap that seq in a `doall`. See this [related issue](https://github.com/reagent-project/reagent/issues/18).
22 |
23 | ### Props Change
24 |
25 | If the props to a Component change, but it doesn't appear to re-render, then the cause will be this rookie mistake: you forgot to repeat the parameters in the inner, anonymous render function.
26 |
27 | ```clj
28 | (defn outer
29 | [a b c] ;; <--- parameters
30 | ;; ....
31 | (fn [a b c] ;; <--- forgetting to repeat parameters here is the mistake
32 | [:div
33 | (str a b c)]))
34 | ```
35 |
36 | If you forget, the component renderer will stubbornly only ever render the
37 | original parameter values, not the updated ones, which can be baffling for
38 | a beginner.
39 |
40 | Remember, `outer` is called once per component instance. The parameters to `outer`
41 | will hold the initial parameter values. The inner renderer on the other hand,
42 | will be called by Reagent many times and, each time, potentially with alternative
43 | parameter values, but unless you repeat the parameters on the renderer it will
44 | close over those initial values in `outer`. As a result, the component renderer
45 | will stubbornly only ever render the original parameter values, not the updated ones.
46 |
47 |
48 | ***
49 |
50 | Up: [FAQ Index](../README.md)
51 |
--------------------------------------------------------------------------------
/doc/FAQ/ForcingComponentRecreation.md:
--------------------------------------------------------------------------------
1 | # Forcing Component re-creation?
2 |
3 | ## React key with a stateful component
4 |
5 | React key can be used to provide identity for components even outside of lists.
6 | If the key changes, the component is recreated triggering all the implemented
7 | stateful methods (did-unmount, did-mount etc.):
8 |
9 | ```clj
10 | (defn comp []
11 | [:div
12 | ^{:key dynamic-id}
13 | [stateful-component]])
14 | ```
15 |
--------------------------------------------------------------------------------
/doc/FAQ/HtmlEntities.md:
--------------------------------------------------------------------------------
1 | # Using HTML entities
2 |
3 | React will escape HTML entities (like ` `, `×`) in the text elements.
4 |
5 | You could just use literal character: `×` or unicode code, which is converted to
6 | the character by Cljs compiler: `\u00D7`.
7 |
8 | HTML entities work in React JSX because JSX will unescape the entity code to
9 | literal character.
10 |
11 | You can do the same in ClojureScript by using `goog.string/unescapeEntities`:
12 |
13 | ```cljs
14 | (ns example
15 | (:require [goog.string :as gstr]))
16 |
17 | (defn comp []
18 | [:h1 "Foo" (gstr/unescapeEntities "×")])
19 | ```
20 |
21 | Note: Yes, this can be inconvenient, but Reagent can't do this automatically as
22 | finding and replacing entities during runtime would be slow.
23 |
--------------------------------------------------------------------------------
/doc/FAQ/MyAttributesAreMissing.md:
--------------------------------------------------------------------------------
1 | ### Question
2 |
3 | Why isn't my attribute `xyz` showing up on ? (where is `xyz` is something like `autoFocus`)
4 |
5 | ### Answer
6 |
7 | You might be spelling it incorrectly.
8 |
9 | React supports [camelCased HTML attributes](https://reactjs.org/docs/dom-elements.html#all-supported-html-attributes),
10 | but the equivalent in Reagent is dashed and lower cased.
11 |
12 | For example, with Reagent, you use `auto-focus`, instead of `autoFocus`. And
13 | you use `col-span` instead of React's `colSpan`.
14 |
15 | ***
16 |
17 | Up: [FAQ Index](../README.md)
18 |
--------------------------------------------------------------------------------
/doc/FAQ/UsingAnEntity.md:
--------------------------------------------------------------------------------
1 | ### Question
2 |
3 | How can I use an entity like "nbsp"?
4 |
5 | ### Answer
6 |
7 | If you try to do this:
8 | ```clj
9 | [:div "hello" " " "there"] ;; <--- note: attempt to use an entity
10 | ```
11 | then you will see the string for the entity. Which is not what you want.
12 |
13 | Instead you should do this:
14 |
15 | 1. Require in goog's string module...
16 |
17 | ```clj
18 | (:require [goog.string :as gstring])
19 | ```
20 |
21 | 2. Use it like this ...
22 |
23 | ```clj
24 | [:div "hello" (gstring/unescapeEntities " ") "there"]
25 | ```
26 |
27 | **Note:** `unescapeEntities` relies on the DOM to produce a string with unescape entities;
28 | in `nodejs` the DOM is unlikely to be available (unless you try using
29 | [`jsdom`](https://www.npmjs.com/package/jsdom-global)).
30 |
31 | ***
32 |
33 | Up: [FAQ Index](../README.md)
34 |
--------------------------------------------------------------------------------
/doc/FAQ/UsingRefs.md:
--------------------------------------------------------------------------------
1 | ### Question
2 |
3 | When using Reagent, how do I use React's `refs`?
4 |
5 | ### Answer
6 |
7 | Credit: this entry is entirely based on Paulus Esterhazy's [Reagent Mysteries series](https://presumably.de/reagent-mysteries-part-3-manipulating-the-dom.html)
8 |
9 | We'll start with a code fragment, because it is worth a 1000 words:
10 |
11 | ```cljs
12 | (defn video-ui []
13 | (let [!video (clojure.core/atom nil)] ;; stores the
14 | (fn [{:keys [src]}]
15 | [:div
16 | [:div
17 | [:video {:src src
18 | :style {:width 400}
19 | :ref (fn [el]
20 | (reset! !video el))}]]
21 | [:div
22 | [:button {:on-click (fn []
23 | (when-let [video @!video] ;; not nil?
24 | (if (.-paused video)
25 | (.play video)
26 | (.pause video))))}
27 | "Toogle"]]])))
28 | ```
29 |
30 | Notes:
31 | 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.
32 | 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).
33 | 3. `!video` is a `clojure.core/atom` and not a `reagent.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.
34 | 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.
35 | 5. Thereafter, `@!video` is used with the `:button's` `:on-click` to manipulate the `video` DOM methods.
36 | 6. For full notes [read Paulus' blog post](https://presumably.de/reagent-mysteries-part-3-manipulating-the-dom.html)
37 | 7. For more background on callback refs, see [React's documentation](https://reactjs.org/docs/refs-and-the-dom.html)
38 |
39 | ***
40 |
41 | Up: [FAQ Index](../README.md)
42 |
--------------------------------------------------------------------------------
/doc/FAQ/dangerouslySetInnerHTML.md:
--------------------------------------------------------------------------------
1 | ### Question
2 |
3 | How can I use React's [dangerouslySetInnerHTML](https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml) feature?
4 |
5 | ### Answer
6 |
7 | A minimal (contrived example):
8 |
9 | ```clj
10 | [:div
11 | {:dangerouslySetInnerHTML
12 | (r/unsafe-html "")}]
13 | ```
14 |
15 | See [Security](../Security.md).
16 |
17 | ***
18 |
19 | Up: [FAQ Index](../README.md)
20 |
--------------------------------------------------------------------------------
/doc/README.md:
--------------------------------------------------------------------------------
1 | ## Tutorials
2 |
3 | 1. [Using Hiccup to Describe HTML](UsingHiccupToDescribeHTML.md)
4 | 2. [Creating Reagent Components](CreatingReagentComponents.md)
5 | 3. [Using [square brackets] instead of (parentheses)](UsingSquareBracketsInsteadOfParens.md)
6 | 4. [When do components update?](WhenDoComponentsUpdate.md)
7 | 5. [[WIP] Managing State: atoms, cursors, Reactions, and tracking](ManagingState.md)
8 | 6. [Batching and Timing: How Reagent Renders Changes to Application State](BatchingAndTiming.md)
9 | 7. [Interop with React](InteropWithReact.md)
10 | 8. [React Features](ReactFeatures.md) and how to use them in Reagent
11 | 8. [Reagent Compiler](ReagentCompiler.md)
12 | 8. [Controlled Inputs](ControlledInputs.md)
13 |
14 | Also:
15 | * [purelyfunctional.tv ](https://purelyfunctional.tv/guide/reagent/) - an excellent, written tutorial
16 | * [Reagent Deep Dive Series by Timothy Pratley](http://timothypratley.blogspot.com.au/p/p.html) - a four part series
17 | * [Reagent Mysteries series by Paulus Esterhazy](https://presumably.de/) - a four part series
18 | * [Props, Children & Component Lifecycle](https://www.martinklepsch.org/posts/props-children-and-component-lifecycle-in-reagent.html) by Martin Klepsch
19 | * [Using Stateful JS Components - like D3](https://github.com/Day8/re-frame/blob/master/docs/Using-Stateful-JS-Components.md) (external link)
20 |
21 | ## Commercial Videos Series
22 |
23 | * [Learn Reagent Free](https://www.jacekschae.com/learn-reagent-free/tycit?coupon=REAGENT)
24 | * [Learn Reagent Pro](https://www.jacekschae.com/learn-reagent-pro/tycit?coupon=REAGENT) (Affiliate link, $30 discount)
25 | * [Learn Re-frame](https://www.jacekschae.com/learn-re-frame-pro?coupon=REAGENT) (Affiliate link, early access)
26 | * [purelyfunctional.tv ](https://purelyfunctional.tv/guide/reagent/)
27 | * [Lambda Island Videos](https://lambdaisland.com/collections/react-reagent-re-frame)
28 |
29 | ## Frequently Asked Questions
30 |
31 | 1. [Why isn't my Component re-rendering?](FAQ/ComponentNotRerendering.md)
32 | 1. [How do I use React's "refs"](FAQ/UsingRefs.md)
33 | 2. [How can I use an entity like "nbsp"?](FAQ/UsingAnEntity.md)
34 | 3. [Why is my attribute (like autoFocus) missing?](FAQ/MyAttributesAreMissing.md)
35 | 4. [How can I use React's dangerouslySetInnerHTML?](FAQ/dangerouslySetInnerHTML.md)
36 | 5. [Reagent doesn't work after updating dependencies](FAQ/CljsjsReactProblems.md)
37 | 5. [How do I force Component re-creation?](FAQ/ForcingComponentRecreation.md)
38 | 5. [Using HTML entities](FAQ/HtmlEntities.md)
39 | 6. [How do I access "props" in lifecycle methods?](http://nils-blum-oeste.net/clojurescripts-reagent-using-props-in-lifecycle-hooks/) (external link)
40 |
41 | ## Examples
42 |
43 | - [MaterialUI v1 with working TextField](examples/material-ui.md), React interop example
44 | - [React-sortable-hoc](../examples/react-sortable-hoc/src/example/core.cljs), React interop example
45 |
46 |
47 | ### Want To Add An FAQ?
48 |
49 | Many Thanks!! We'd like that:
50 | 1. As a base, just use the structure from one of the existing FAQs files
51 | 2. Give us a PR which includes your new file AND a change to this README so your entry is listed.
52 |
53 | #### Misc Docs
54 |
55 | - [0.8-upgrade](0.8-upgrade.md)
56 | - [development](development.md)
57 |
--------------------------------------------------------------------------------
/doc/ReactRenderers.md:
--------------------------------------------------------------------------------
1 | # React Renderers
2 |
3 | React itself is not limited to HTML and the DOM.
4 | [ReactDOM](https://react.dev/reference/react-dom) is one implementation that
5 | renders React elements into the DOM and HTML.
6 |
7 | Reagent does not have test cases for other renderers, but there should be
8 | nothing in its implementation that is specifically tied to the DOM or HTML,
9 | meaning it should work with other rendering targets.
10 |
11 | Reagent only requires the `react-dom` module in the `reagent.dom`,
12 | `reagent.dom.client`, and `reagent.dom.server` namespaces. This means you can
13 | use `reagent.core` with other targets without unintentionally requiring
14 | `react-dom`.
15 |
16 | ## Other Rendering Targets
17 |
18 | One of the most common alternative targets is [React
19 | Native](https://reactnative.dev/). Reagent has been successfully used with
20 | React Native applications for years.
21 |
22 | Reagent does not provide built-in `render` functions for non-DOM targets, so
23 | you will need to handle rendering manually. This is typically done using
24 | `reagent.core/as-element` to convert a Reagent Hiccup form into a React element
25 | for use with a renderer, or `reagent.core/reactify-component` to turn a
26 | function into a React component.
27 |
28 | Example:
29 |
30 | ```clj
31 | (ns app.main
32 | (:require ["react-native" :refer [View Text AppRegistry]]))
33 |
34 | (defn root []
35 | [:> View [:> Text "Hello world"]])
36 |
37 | ;; Using reactify-component to create a React component
38 | (def Root (r/reactify-component root))
39 |
40 | ;; Using as-element, where the function itself returns a React element
41 | (defn Root []
42 | (r/as-element [root]))
43 |
44 | ;; React Native entry point
45 | (defn init []
46 | (.registerComponent AppRegistry "App" (fn [] Root)))
47 | ```
48 |
49 | ### Other Examples of Alternative Render Targets
50 |
51 | - [react-three-fiber](https://r3f.docs.pmnd.rs/getting-started/introduction) (for 3D rendering)
52 | - [Ink](https://github.com/vadimdemedes/ink) (for rendering React components in the terminal)
53 |
54 | ## Reporting Issues
55 |
56 | If you encounter issues with other rendering targets, consider opening an
57 | issue. Providing a minimal example repository that reproduces the problem will
58 | be extremely helpful, as the Reagent project does not include examples for all
59 | possible render targets.
60 |
--------------------------------------------------------------------------------
/doc/ReagentCompiler.md:
--------------------------------------------------------------------------------
1 | # Reagent Compiler
2 |
3 | Reagent Compiler object is a new way to configure how Reagent
4 | turns the Hiccup-style markup into React components and elements.
5 |
6 | As a first step, this can be used to turn on option to create
7 | functional components when a function is referred in a Hiccup vector:
8 | `[component-fn parameters]`.
9 |
10 | Read more about Hooks
11 |
12 | ```cljs
13 | (def functional-compiler (reagent.core/create-compiler {:function-components true}))
14 |
15 | ;; Using the option
16 | (reagent.dom/render [main] div functional-compiler)
17 | (reagent.core/as-element [main] functional-compiler)
18 | ;; Setting compiler as the default
19 | (reagent.core/set-default-compiler! functional-compiler)
20 | ```
21 |
22 | ## Functional components implementation
23 |
24 | Features:
25 |
26 | - Ratoms works.
27 | - The functions are wrapped in another function, which uses two
28 | state hooks to store component identity and "update count" - which is used to
29 | force re-render when Ratoms the component uses are updated.
30 | - The functions is wrapped in `react/memo` to implement logic similar to
31 | `shouldComponentUpdate` (component is rendered only if the properties change).
32 | - This implementation passes the same test suite as class components.
33 |
34 | Differences to Class component implementation:
35 |
36 | - `reagent.dom/render` doesn't return the Component instance, but just `nil`
37 | - `reagent.core/current-component` returns a mocked object that can be passed to `reagent.core/force-update`,
38 | but won't support everything that real Component instance would support.
39 | - A bit slower compared to Class component implementation
40 | - `useEffect` cleanup function is called asynchronously some time after
41 | unmounting the component from DOM (in React 17). This is used to dispose component RAtom,
42 | which will affect e.g. `r/with-let` `finally` function being called. Cleanup
43 | is still called before the component is mounted again. This probably shouldn't
44 | affect any real use cases, but required waiting two animation frames on
45 | Reagent tests to assert that the `finally` was ran.
46 | ([More information](https://reactjs.org/blog/2020/08/10/react-v17-rc.html#effect-cleanup-timing))
47 | - Using `r/wrap` as component parameter seems to in some cases re-render
48 | components when source atom is changed, even if the value in path didn't
49 | change. Could be related to how `react/memo` handles changes properties.
50 |
51 | 
52 |
53 | (Local test run with https://github.com/krausest/js-framework-benchmark, with added function component case)
54 |
55 | ## Reasoning
56 |
57 | Now that this mechanism to control how Reagent compiles Hiccup-style markup
58 | to React calls is in place, it will be probably used later to control
59 | some other things also:
60 |
61 | From [Clojurist Together announcenment](https://www.clojuriststogether.org/news/q1-2020-funding-announcement/):
62 |
63 | > As this [hooks] affects how Reagent turns Hiccup to React elements and components, I
64 | > have some ideas on allowing users configure the Reagent Hiccup compiler,
65 | > similar to what [Hicada](https://github.com/rauhs/hicada) does. This would also allow introducing optional
66 | > features which would break existing Reagent code, by making users opt-in to
67 | > these. One case would be to make React component interop simpler.
68 |
69 | Some ideas:
70 |
71 | - Providing options to control how component parameters are converted to JS
72 | objects (or even disable automatic conversion)
73 | - Implement support for custom tags (if you can provide your own function
74 | to create element from a keyword, this will be easy)
75 |
76 | Open questions:
77 |
78 | - Will this cause problems for libraries? Do the libraries have to start
79 | calling `as-element` with their own Compiler to ensure compatibility.
80 |
--------------------------------------------------------------------------------
/doc/Security.md:
--------------------------------------------------------------------------------
1 | # Security and Reagent
2 |
3 | ## Hiccup Data
4 |
5 | Reagent uses Clojure data structures to represent the React elements that will
6 | be created and mounted on the page.
7 |
8 | In most cases, React does its best to avoid code injection attacks by making it
9 | difficult to accidentally create elements that execute JavaScript code. For
10 | example, `
13 |