Merge branch 'develop' of https://github.com/Day8/re-frame into develop

This commit is contained in:
Mike Thompson 2016-11-30 21:28:12 +11:00
commit e95d07da32
47 changed files with 756 additions and 188 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
CHANGES.md merge=union

View File

@ -6,6 +6,8 @@
<ClojureCodeStyleSettings>{
:cljs.core/with-redefs 1
:cursive.formatting/align-binding-forms true
:re-frame.trace/register-trace-cb :only-indent
:re-frame.trace/with-trace 1
}</ClojureCodeStyleSettings>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />

View File

@ -2,15 +2,24 @@
#### Headline
- [#233](https://github.com/Day8/re-frame/issues/233) Added a new builtin effect handler for de-bouncing events
- [#218](https://github.com/Day8/re-frame/issues/218) Make it okay to use `subscribe` in Form-1 components
#### Breaking
- Due to the new tracing features using `goog-define` (described below), re-frame now requires ClojureScript 1.7.48 or above. See [Parameterizing ClojureScript Builds](https://www.martinklepsch.org/posts/parameterizing-clojurescript-builds.html) for more information.
#### Improvements
- [#200](https://github.com/Day8/re-frame/pull/200) Remove trailing spaces from console logging
- [#248](https://github.com/Day8/re-frame/pull/200) Provide after interceptor with `db` coeffect, if no `db` effect was produced.
- [#248](https://github.com/Day8/re-frame/pull/248) Provide after interceptor with `db` coeffect, if no `db` effect was produced.
- Add re-frame.loggers/get-loggers function to well, you know.
- Added `clear-subscription-cache!` function. This should be used when hot reloading code to ensure that any bad subscriptions that cause rendering exceptions are removed. See [reagent-project/reagent#272](https://github.com/reagent-project/reagent/issues/272) for more details on this.
- Added experimental tracing features. These are subject to change and undocumented at the moment. By default they are disabled, and will be completely compiled out by advanced optimisations. To enable them, set a [`:closure-defines`](https://www.martinklepsch.org/posts/parameterizing-clojurescript-builds.html) key to `{"re_frame.trace.trace_enabled_QMARK_" true}`
#### Fixes
- [#259](https://github.com/Day8/re-frame/pull/259) Fix a bug where registering a subscription would create and close over dependent subscriptions, meaning that they would never be garbage collected, and doing more work than necessary.
- Fix a bug where subscribing to a subscription that didn't exist would throw an exception, instead of returning nil.
## 0.8.0 (2016.08.19)

View File

@ -12,7 +12,6 @@ the [ClojureScript mailing list](https://groups.google.com/forum/#!forum/clojure
**Create pull requests to the develop branch**, work will be merged onto master when it is ready to be released.
## Running tests
#### Via Browser/HTML
@ -60,3 +59,9 @@ If possible provide:
* Docstrings for functions
* Documentation examples
* Add the change to the Unreleased section of [CHANGES.md](CHANGES.md)
## Pull requests for docs
* Make your documentation changes
* (Optional) Install doctoc with `npm install -g doctoc`
* (Optional) Regenerate the docs TOC with `bin/doctoc.sh` or `bin/doctoc.bat` depending on your OS

6
bin/doctoc.bat Executable file
View File

@ -0,0 +1,6 @@
:: Table of contents are generated by doctoc.
:: Install doctoc with `npm install -g doctoc`
:: Then run this script to regenerate the TOC after
:: editing the docs.
doctoc ./docs/ --github --title '## Table Of Contents'

7
bin/doctoc.sh Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
# Table of contents are generated by doctoc.
# Install doctoc with `npm install -g doctoc`
# Then run this script to regenerate the TOC after
# editing the docs.
doctoc $(dirname $0)/../docs --github --title '## Table Of Contents'

View File

@ -1,3 +1,13 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Simpler Apps](#simpler-apps)
- [There's A Small Gotcha](#theres-a-small-gotcha)
- [Larger Apps](#larger-apps)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Simpler Apps
To build a re-frame app, you:
@ -58,7 +68,8 @@ src
Continue to [Navigation](Navigation.md) to learn how to switch between panels of a larger app.
---
***
Previous: [CoEffects](coeffects.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Up: [Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Next: [Navigation](Navigation.md)

View File

@ -4,24 +4,26 @@ This tutorial explains `coeffects`.
It explains what they are, how they can be "injected", and how
to manage them in tests.
## Table Of Contexts
* [What Are They?](#what-are-they-)
* [An Example](#an-example)
* [How We Want It](#how-we-want-it)
* [Abracadabra](#abracadabra)
* [Which Interceptors?](#which-interceptors-)
* [`inject-cofx`](#-inject-cofx-)
* [More `inject-cofx`](#more--inject-cofx-)
* [Meet `reg-cofx`](#meet--reg-cofx-)
* [Example Of `reg-cofx`](#example-of--reg-cofx-)
* [Another Example Of `reg-cofx`](#another-example-of--reg-cofx-)
* [Secret Interceptors](#secret-interceptors)
* [Testing](#testing)
* [The 5 Point Summary](#the-5-point-summary)
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
## Coeffects
- [What Are They?](#what-are-they)
- [An Example](#an-example)
- [How We Want It](#how-we-want-it)
- [Abracadabra](#abracadabra)
- [Which Interceptors?](#which-interceptors)
- [`inject-cofx`](#inject-cofx)
- [More `inject-cofx`](#more-inject-cofx)
- [Meet `reg-cofx`](#meet-reg-cofx)
- [Example Of `reg-cofx`](#example-of-reg-cofx)
- [Another Example Of `reg-cofx`](#another-example-of-reg-cofx)
- [Secret Interceptors](#secret-interceptors)
- [Testing](#testing)
- [The 5 Point Summary](#the-5-point-summary)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
### What Are They?
@ -267,7 +269,8 @@ In note form:
5. We must have previously registered a cofx handler via `reg-cofx`
---
***
Previous: [Effects](Effects.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Up: [Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Next: [Basic App Structure](Basic-App-Structure.md)

View File

@ -6,6 +6,19 @@ Event handlers are quite central to a re-frame app. Only event handlers
can update `app-db`, to "step" an application "forward" from one state
to the next.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [The `debug` Interceptor](#the-debug-interceptor)
- [Using `debug`](#using-debug)
- [Too Much Repetition - Part 1](#too-much-repetition---part-1)
- [3. Checking DB Integrity](#3-checking-db-integrity)
- [Too Much Repetition - Part 2](#too-much-repetition---part-2)
- [What about the -fx variation?](#what-about-the--fx-variation)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## The `debug` Interceptor
You might wonder: is my event handler making the right changes to `app-db`?

View File

@ -4,6 +4,25 @@ This page describes a technique for
debugging re-frame apps. It proposes a particular combination
of tools. By the end, you'll be better at dominos.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Know The Beast!](#know-the-beast)
- [re-frame's Step 3](#re-frames-step-3)
- [Observe The Beast](#observe-the-beast)
- [How To Trace?](#how-to-trace)
- [Your browser](#your-browser)
- [Your Project](#your-project)
- [Say No To Anonymous](#say-no-to-anonymous)
- [IMPORTANT](#important)
- [The result](#the-result)
- [Warning](#warning)
- [React Native](#react-native)
- [Appendix A - Prior to V0.8.0](#appendix-a---prior-to-v080)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Know The Beast!
re-frame apps are **event driven**.

View File

@ -3,27 +3,30 @@
This tutorial shows you how to implement pure event handlers that side-effect.
Yes, a surprising claim.
## Table Of Contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
* [Events Happen](#events-happen)
* [Handling The Happening](#handling-the-happening)
* [Your Handling](#your-handling)
* [90% Solution](#90--solution)
* [Bad, Why?](#bad--why-)
* [The 2nd Kind Of Problem](#the-2nd-kind-of-problem)
* [Effects And Coeffects](#effects-and-coeffects)
* [Why Does This Happen?](#why-does-this-happen-)
* [Doing vs Causing](#doing-vs-causing)
* [Et tu, React?](#et-tu--react-)
* [Pattern Structure](#pattern-structure)
* [Effects: The Two Step Plan](#effects--the-two-step-plan)
* [Step 1 Of Plan](#step-1-of-plan)
* [Another Example](#another-example)
* [The Coeffects](#the-coeffects)
* [Variations On A Theme](#variations-on-a-theme)
* [Summary](#summary)
- [Events Happen](#events-happen)
- [Handling The Happening](#handling-the-happening)
- [Your Handling](#your-handling)
- [90% Solution](#90%25-solution)
- [Bad, Why?](#bad-why)
- [The 2nd Kind Of Problem](#the-2nd-kind-of-problem)
- [Effects And Coeffects](#effects-and-coeffects)
- [Why Does This Happen?](#why-does-this-happen)
- [Doing vs Causing](#doing-vs-causing)
- [Et tu, React?](#et-tu-react)
- [Pattern Structure](#pattern-structure)
- [Effects: The Two Step Plan](#effects-the-two-step-plan)
- [Step 1 Of Plan](#step-1-of-plan)
- [Another Example](#another-example)
- [The Coeffects](#the-coeffects)
- [Variations On A Theme](#variations-on-a-theme)
- [Summary](#summary)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Effects
### Events Happen
Events "happen" when they are dispatched.
@ -392,7 +395,8 @@ In the next tutorial, we'll shine a light on `interceptors` which are
the mechanism by which event handlers are executed. That knowledge will give us a springboard
to then, as a next step, better understand coeffects and effects. We'll soon be writing our own.
---
***
Up: [Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Next: [Interceptors](Interceptors.md)

View File

@ -10,24 +10,30 @@ make side effects a noop in event replays.
> &nbsp; &nbsp; -- @stuarthalloway
## Table Of Contexts
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
* [Where Effects Come From](#where-effects-come-from)
* [The Effects Map](#the-effects-map)
* [Infinite Effects](#infinite-effects)
* [Extensible Side Effects](#extensible-side-effects)
* [Writing An Effect Handler](#writing-an-effect-handler)
* [:db Not Always Needed](#-db-not-always-needed)
* [What Makes This Work?](#what-makes-this-work-)
* [Order Of Effects?](#order-of-effects-)
* [Effects With No Data](#effects-with-no-data)
* [Testing And Noops](#testing-and-noops)
* [Builtin Effect Handlers](#builtin-effect-handlers)
+ [:dispatch-later](#dispatch-later)
+ [:dispatch](#dispatch)
+ [:dispatch-n](#dispatch-n)
+ [:deregister-event-handler](#deregister-event-handler)
+ [:db](#db)
- [Where Effects Come From](#where-effects-come-from)
- [The Effects Map](#the-effects-map)
- [Infinite Effects](#infinite-effects)
- [Extensible Side Effects](#extensible-side-effects)
- [Writing An Effect Handler](#writing-an-effect-handler)
- [:db Not Always Needed](#db-not-always-needed)
- [What Makes This Work?](#what-makes-this-work)
- [Order Of Effects?](#order-of-effects)
- [Effects With No Data](#effects-with-no-data)
- [Testing And Noops](#testing-and-noops)
- [Summary](#summary)
- [Builtin Effect Handlers](#builtin-effect-handlers)
- [:dispatch-later](#dispatch-later)
- [:dispatch](#dispatch)
- [:dispatch-n](#dispatch-n)
- [:deregister-event-handler](#deregister-event-handler)
- [:db](#db)
- [External Effects](#external-effects)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
### Where Effects Come From
@ -283,13 +289,6 @@ registered handlers) to which you can return.
XXX
---
Previous: [Interceptors](Interceptors.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Up: [Index](Readme.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Next: [Coeffects](Coeffects.md)
---
### Builtin Effect Handlers
#### :dispatch-later
@ -359,7 +358,8 @@ Create a PR to include yours in this list.
XXX maybe put this list into the Wiki, so editable by all.
---
***
Previous: [Interceptors](Interceptors.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Up: [Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Next: [Coeffects](Coeffects.md)

View File

@ -2,6 +2,16 @@
Templates, examples, and other resources related to re-frame. For additions or modifications, please create an issue with a link and description or submit a pull request.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Templates](#templates)
- [Examples and Applications Using re-frame](#examples-and-applications-using-re-frame)
- [Videos](#videos)
- [Server Side Rendering](#server-side-rendering)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
### Templates

View File

@ -1,3 +1,14 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Question](#question)
- [Short Answer](#short-answer)
- [Better Answer](#better-answer)
- [Other Inspection Tools](#other-inspection-tools)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
### Question
@ -61,5 +72,6 @@ There's also [Data Frisk](https://github.com/Odinodin/data-frisk-reagent) which
provides a very nice solution for navigating and inspecting any data structure.
---
***
Up: [FAQ Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

View File

@ -1,3 +1,12 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Question](#question)
- [Answer](#answer)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
### Question
I use logging method X, how can I make re-frame use my method?
@ -23,5 +32,6 @@ override that by providing your own set or subset of these functions using
...})
```
---
***
Up: [FAQ Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

View File

@ -1,3 +1,12 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Question](#question)
- [Answer](#answer)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
### Question
If I `dispatch` a js event object (from a view), it is nullified
@ -17,5 +26,6 @@ So there's two things to say about this:
and debugging will become easier.
---
***
Up: [FAQ Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

View File

@ -1,3 +1,12 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Frequently Asked Questions](#frequently-asked-questions)
- [Want To Add An FAQ?](#want-to-add-an-faq)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Frequently Asked Questions
1. [How can I Inspect app-db?](Inspecting-app-db.md)

View File

@ -1,3 +1,12 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Question](#question)
- [Answer](#answer)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
### Question
Why is re-frame implemented in `.cljc` files? Aren't ClojureScript
@ -16,5 +25,6 @@ Necessary interop for each platform can be found in
See also: https://github.com/Day8/re-frame-test
---
***
Up: [FAQ Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

View File

@ -1,27 +1,34 @@
## re-frame Interceptors
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Introduction](#introduction)
- [Interceptors](#interceptors)
- [Why Interceptors?](#why-interceptors)
- [What Do Interceptors Do?](#what-do-interceptors-do)
- [Wait, I know That Pattern!](#wait-i-know-that-pattern)
- [What's In The Pipeline?](#whats-in-the-pipeline)
- [Show Me](#show-me)
- [Handlers Are Interceptors Too](#handlers-are-interceptors-too)
- [Executing A Chain](#executing-a-chain)
- [The Links Of The Chain](#the-links-of-the-chain)
- [What Is Context?](#what-is-context)
- [Self Modifying](#self-modifying)
- [Credit](#credit)
- [Write An Interceptor](#write-an-interceptor)
- [Wrapping Handlers](#wrapping-handlers)
- [Summary](#summary)
- [Appendix](#appendix)
- [The Builtin Interceptors](#the-builtin-interceptors)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Introduction
This is a tutorial on re-frame Interceptors.
## Table Of Contents
- [Interceptors](#interceptors)
* [Why Interceptors?](#why-interceptors-)
* [What Do Interceptors Do?](#what-do-interceptors-do-)
* [Wait, I know That Pattern!](#wait--i-know-that-pattern-)
* [What's In The Pipeline?](#what-s-in-the-pipeline-)
* [Show Me](#show-me)
* [Handlers Are Interceptors Too](#handlers-are-interceptors-too)
- [Executing A Chain](#executing-a-chain)
* [The Links Of The Chain](#the-links-of-the-chain)
* [What Is Context?](#what-is-context-)
* [Self Modifying](#self-modifying)
* [Credit](#credit)
* [Write An Interceptor](#write-an-interceptor)
* [Wrapping Handlers](#wrapping-handlers)
- [Summary](#summary)
- [Appendix](#appendix)
* [The Builtin Interceptors](#the-builtin-interceptors)
## Interceptors
### Why Interceptors?
@ -382,7 +389,8 @@ To use them, first require them:
[re-frame.core :refer [debug path]])
```
---
***
Previous: [Effectful Handlers](EffectfulHandlers.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Up: [Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Next: [Effects](Effects.md)

View File

@ -1,3 +1,19 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Bootstrapping Application State](#bootstrapping-application-state)
- [1. Register Handlers](#1-register-handlers)
- [2. Kick Start Reagent](#2-kick-start-reagent)
- [3. Loading Initial Data](#3-loading-initial-data)
- [Getting Data Into `app-db`](#getting-data-into-app-db)
- [The Pattern](#the-pattern)
- [Scales Up](#scales-up)
- [Cheating - Synchronous Dispatch](#cheating---synchronous-dispatch)
- [Loading Initial Data From Services](#loading-initial-data-from-services)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Bootstrapping Application State
To bootstrap a re-frame application, you need to:
@ -218,7 +234,8 @@ The next Tutorial will show you how.
---
***
Previous: [Namespaced Keywords](Namespaced-Keywords.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Up: [Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Next: [Talking To Servers](Talking-To-Servers.md)

View File

@ -1,3 +1,11 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Namespaced Ids](#namespaced-ids)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Namespaced Ids
As an app gets bigger, you'll tend to get clashes on ids - event-ids, or query-ids (subscriptions), etc.
@ -20,7 +28,8 @@ fiction. I can have the keyword `:panel1/edit` even though
Naturally, you'll take advantage of this by using keyword namespaces
which are both unique and descriptive.
---
***
Previous: [Navigation](Navigation.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Up: [Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Next: [Loading Initial Data](Loading-Initial-Data.md)
Next: [Loading Initial Data](Loading-Initial-Data.md)

View File

@ -1,3 +1,11 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [What About Navigation?](#what-about-navigation)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## What About Navigation?
@ -49,7 +57,8 @@ A high level reagent view has a subscription to :active-panel and will switch to
Continue to [Namespaced Keywords](Namespaced-Keywords.md) to reduce clashes on ids.
---
***
Previous: [Basic App Structure](Basic-App-Structure.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Up: [Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Next: [Namespaced Keywords](Namespaced-Keywords.md)

View File

@ -1,5 +1,19 @@
## Eek! Performance Problems
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [1. Is It The `debug` Interceptor?](#1-is-it-the-debug-interceptor)
- [2. `=` On Big Structures](#2--on-big-structures)
- [An Example Of Problem 2](#an-example-of-problem-2)
- [Solutions To Problem 2](#solutions-to-problem-2)
- [3. Are you Using a React `key`?](#3-are-you-using-a-react-key)
- [4. Callback Functions](#4-callback-functions)
- [A Weapon](#a-weapon)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## 1. Is It The `debug` Interceptor?
This first one is something of a non-problem.

View File

@ -36,3 +36,11 @@
- [Solve the CPU hog problem](Solve-the-CPU-hog-problem.md)
- [Using Stateful JS Components](Using-Stateful-JS-Components.md)
- [The re-frame Logo](The-re-frame-logo.md)
<!-- DOCTOC SKIP
We put these inside a comment so that they are hidden when rendered on Github.
<!-- START doctoc -->
<!-- END doctoc -->
-->

View File

@ -1,3 +1,18 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Solving The CPU Hog Problem](#solving-the-cpu-hog-problem)
- [The re-frame Solution](#the-re-frame-solution)
- [A Sketch](#a-sketch)
- [Why Does A Redispatch Work?](#why-does-a-redispatch-work)
- [Variations](#variations)
- [Cancel Button](#cancel-button)
- [Further Notes](#further-notes)
- [Forcing A One Off Render](#forcing-a-one-off-render)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Solving The CPU Hog Problem
Sometimes a handler has a lot of CPU intensive work to do, and

View File

@ -1,3 +1,23 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Subscribing to External Data](#subscribing-to-external-data)
- [There Can Be Only One!!](#there-can-be-only-one)
- [Components Don't Know, Don't Care](#components-dont-know-dont-care)
- [A 2nd Source](#a-2nd-source)
- [Via A Subscription](#via-a-subscription)
- [The Subscription Handler's Job](#the-subscription-handlers-job)
- [Some Code](#some-code)
- [Any Good?](#any-good)
- [Warning: Undo/Redo](#warning-undoredo)
- [Query De-duplication](#query-de-duplication)
- [Thanks To](#thanks-to)
- [The Alternative Approach](#the-alternative--approach)
- [What Not To Do](#what-not-to-do)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Subscribing to External Data
In [Talking To Servers](Talking-To-Servers.md) we learned how to
@ -236,6 +256,7 @@ data into HTML and nothing more. they absolutely do not do imperative stuff.
Use one of the two alternatives described above.
---
***
Previous: [Talking to Servers](Talking-To-Servers.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Up: [Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

View File

@ -1,3 +1,17 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Talking To Servers](#talking-to-servers)
- [Triggering The Request](#triggering-the-request)
- [The Event Handler](#the-event-handler)
- [Version 1](#version-1)
- [Successful GET](#successful-get)
- [Problems In Paradise?](#problems-in-paradise)
- [Version 2](#version-2)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Talking To Servers
This page describes how a re-frame app might "talk" to a backend HTTP server.
@ -141,7 +155,8 @@ Notes:
---
***
Previous: [Loading Initial Data](Loading-Initial-Data.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Up: [Index](README.md)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Next: [Subscribing to External Data](Subscribing-To-External-Data.md)

View File

@ -1,3 +1,19 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Testing](#testing)
- [Event Handlers - Part 1](#event-handlers---part-1)
- [Event Handlers - Part 2](#event-handlers---part-2)
- [Subscription Handlers](#subscription-handlers)
- [Components- Part 1](#components--part-1)
- [Components - Part 2A](#components---part-2a)
- [Components - Part 2B](#components---part-2b)
- [Components - Part 2C](#components---part-2c)
- [Summary](#summary)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
> IGNORE THIS DOCUMENT. IT IS WIP
## Testing

View File

@ -1,3 +1,14 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [The re-frame Logo](#the-re-frame-logo)
- [Who](#who)
- [Genesis Theories](#genesis-theories)
- [Assets Where?](#assets-where)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## The re-frame Logo
![logo](/images/logo/re-frame_256w.png?raw=true)

View File

@ -13,6 +13,19 @@ thrill of that forbidden fruit.
I won't tell, if you don't. But careful plans must be made ...
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [The overall plan](#the-overall-plan)
- [Example Using Google Maps](#example-using-google-maps)
- [Pattern Discovery](#pattern-discovery)
- [Code Credit](#code-credit)
- [D3 Examples](#d3-examples)
- [Advanced Lifecycle Methods](#advanced-lifecycle-methods)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
### The overall plan

View File

@ -2,6 +2,17 @@
Let's understand how re-frame manages application state.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [On Data](#on-data)
- [The Big Ratom](#the-big-ratom)
- [The Benefits Of Data-In-The-One-Place](#the-benefits-of-data-in-the-one-place)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
### On Data
<blockquote class="twitter-tweet" lang="en"><p>Well-formed Data at rest is as close to perfection in programming as it gets. All the crap that had to happen to put it there however...</p>&mdash; Fogus (@fogus) <a href="https://twitter.com/fogus/status/454582953067438080">April 11, 2014</a></blockquote>

View File

@ -1,3 +1,26 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Initial Code Walk-through](#initial-code-walk-through)
- [What Code?](#what-code)
- [What Does It Do?](#what-does-it-do)
- [Namespace](#namespace)
- [Data Schema](#data-schema)
- [Events (domino 1)](#events-domino-1)
- [dispatch](#dispatch)
- [After dispatch](#after-dispatch)
- [Event Handlers (domino 2)](#event-handlers-domino-2)
- [reg-event-db](#reg-event-db)
- [:initialize](#initialize)
- [:timer](#timer)
- [:time-color-change](#time-color-change)
- [Effect Handlers (domino 3)](#effect-handlers-domino-3)
- [Subscription Handlers (domino 4)](#subscription-handlers-domino-4)
- [View Functions (domino 5)](#view-functions-domino-5)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Initial Code Walk-through
At this point in your reading, you are armed with:

View File

@ -1,3 +1,22 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Mental Model Omnibus](#mental-model-omnibus)
- [What is the problem?](#what-is-the-problem)
- [Guiding Philosophy](#guiding-philosophy)
- [It Does Physics](#it-does-physics)
- [It does Event Sourcing](#it-does-event-sourcing)
- [It does a reduce](#it-does-a-reduce)
- [It does FSM](#it-does-fsm)
- [Data Oriented Design](#data-oriented-design)
- [Derived Data](#derived-data)
- [Prefer Dumb Views - Part 1](#prefer-dumb-views---part-1)
- [Prefer Dumb Views - Part 2](#prefer-dumb-views---part-2)
- [Full Stack](#full-stack)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Mental Model Omnibus
> If a factory is torn down but the rationality which produced it is

View File

@ -1,3 +1,21 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Derived Values, Flowing](#derived-values-flowing)
- [Why Should You Care?](#why-should-you-care)
- [re-frame](#re-frame)
- [It Is A Loop](#it-is-a-loop)
- [It Has 5 Dominoes](#it-has-5-dominoes)
- [A Dominoes Walk Through](#a-dominoes-walk-through)
- [A Simple Loop Of Simple Functions](#a-simple-loop-of-simple-functions)
- [It Leverages Data](#it-leverages-data)
- [It is both mature and successful in the large](#it-is-both-mature-and-successful-in-the-large)
- [Where Do I Go Next?](#where-do-i-go-next)
- [Licence](#licence)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
[logo](/images/logo/re-frame_128w.png?raw=true)
## Derived Values, Flowing

View File

@ -1,3 +1,21 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table Of Contents
- [Implements Reactive Data Flows](#implements-reactive-data-flows)
- [Flow](#flow)
- [Reactive Programming](#reactive-programming)
- [How Flow Happens In Reagent](#how-flow-happens-in-reagent)
- [Components](#components)
- [Truth Interlude](#truth-interlude)
- [React etc.](#react-etc)
- [Subscribe](#subscribe)
- [Just A Read-Only Cursor?](#just-a-read-only-cursor)
- [The Signal Graph](#the-signal-graph)
- [A More Efficient Signal Graph](#a-more-efficient-signal-graph)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->

View File

@ -1,4 +1,4 @@
(defproject re-frame "0.8.1-SNAPSHOT"
(defproject re-frame "0.9.0-alpha2-SNAPSHOT"
:description "A Clojurescript MVC-like Framework For Writing SPAs Using Reagent."
:url "https://github.com/Day8/re-frame.git"
:license {:name "MIT"}
@ -47,7 +47,7 @@
:cljsbuild {:builds [{:id "test"
:source-paths ["test" "src"]
:compiler {:preloads [devtools.preload]
:external-config {:devtools/config {:features-to-install :all}}
:external-config {:devtools/config {:features-to-install [:formatters :hints]}}
:output-to "run/compiled/browser/test.js"
:source-map true
:output-dir "run/compiled/browser/test"
@ -61,7 +61,8 @@
:output-dir "run/compiled/karma/test"
:optimizations :whitespace
:main "re_frame.test_runner"
:pretty-print true}}]}
:pretty-print true
:closure-defines {"re_frame.trace.trace_enabled_QMARK_" true}}}]}
:aliases {"test-once" ["do" "clean," "cljsbuild" "once" "test," "shell" "open" "test/test.html"]
"test-auto" ["do" "clean," "cljsbuild" "auto" "test,"]

View File

@ -58,6 +58,7 @@
(def subscribe subs/subscribe)
(def clear-sub (partial registrar/clear-handlers subs/kind))
(def clear-subscription-cache! subs/clear-subscription-cache!)
;; -- effects
(def reg-fx fx/register)

View File

@ -4,7 +4,8 @@
[re-frame.interop :refer [empty-queue debug-enabled?]]
[re-frame.registrar :refer [get-handler register-handler]]
[re-frame.loggers :refer [console]]
[re-frame.interceptor :as interceptor]))
[re-frame.interceptor :as interceptor]
[re-frame.trace :as trace :include-macros true]))
(def kind :event)
@ -56,6 +57,9 @@
(if *handling*
(console :error (str "re-frame: while handling \"" *handling* "\", dispatch-sync was called for \"" event-v "\". You can't call dispatch-sync within an event handler."))
(binding [*handling* event-v]
(interceptor/execute event-v interceptors))))))
(trace/with-trace {:operation event-id
:op-type kind
:tags {:event event-v}}
(interceptor/execute event-v interceptors)))))))

View File

@ -71,3 +71,8 @@
there isn't often much point firing a timed event in a test."
[f ms]
(next-tick f))
(defn now []
;; currentTimeMillis may count backwards in some scenarios, but as this is used for tracing
;; it is preferable to the slower but more accurate System.nanoTime.
(System/currentTimeMillis))

View File

@ -36,3 +36,8 @@
(defn set-timeout! [f ms]
(js/setTimeout f ms))
(defn now []
(if (exists? js/performance.now)
(js/performance.now)
(js/Date.now)))

View File

@ -1,7 +1,8 @@
(ns re-frame.router
(:require [re-frame.events :refer [handle]]
[re-frame.interop :refer [after-render empty-queue next-tick]]
[re-frame.loggers :refer [console]]))
[re-frame.loggers :refer [console]]
[re-frame.trace :as trace :include-macros true]))
;; -- Router Loop ------------------------------------------------------------
@ -122,41 +123,46 @@
;; Given a "trigger", and the existing FSM state, it computes the
;; new FSM state and the transition action (function).
(let [[new-fsm-state action-fn]
(case [fsm-state trigger]
(trace/with-trace {:op-type ::fsm-trigger}
(let [[new-fsm-state action-fn]
(case [fsm-state trigger]
;; You should read the following "case" as:
;; [current-FSM-state trigger] -> [new-FSM-state action-fn]
;;
;; So, for example, the next line should be interpreted as:
;; if you are in state ":idle" and a trigger ":add-event"
;; happens, then move the FSM to state ":scheduled" and execute
;; that two-part "do" function.
[:idle :add-event] [:scheduled #(do (-add-event this arg)
(-run-next-tick this))]
;; You should read the following "case" as:
;; [current-FSM-state trigger] -> [new-FSM-state action-fn]
;;
;; So, for example, the next line should be interpreted as:
;; if you are in state ":idle" and a trigger ":add-event"
;; happens, then move the FSM to state ":scheduled" and execute
;; that two-part "do" function.
[:idle :add-event] [:scheduled #(do (-add-event this arg)
(-run-next-tick this))]
;; State: :scheduled (the queue is scheduled to run, soon)
[:scheduled :add-event] [:scheduled #(-add-event this arg)]
[:scheduled :run-queue] [:running #(-run-queue this)]
;; State: :scheduled (the queue is scheduled to run, soon)
[:scheduled :add-event] [:scheduled #(-add-event this arg)]
[:scheduled :run-queue] [:running #(-run-queue this)]
;; State: :running (the queue is being processed one event after another)
[:running :add-event ] [:running #(-add-event this arg)]
[:running :pause ] [:paused #(-pause this arg)]
[:running :exception ] [:idle #(-exception this arg)]
[:running :finish-run] (if (empty? queue) ;; FSM guard
[:idle]
[:scheduled #(-run-next-tick this)])
;; State: :running (the queue is being processed one event after another)
[:running :add-event] [:running #(-add-event this arg)]
[:running :pause] [:paused #(-pause this arg)]
[:running :exception] [:idle #(-exception this arg)]
[:running :finish-run] (if (empty? queue) ;; FSM guard
[:idle]
[:scheduled #(-run-next-tick this)])
;; State: :paused (:flush-dom metadata on an event has caused a temporary pause in processing)
[:paused :add-event] [:paused #(-add-event this arg)]
[:paused :resume ] [:running #(-resume this)]
;; State: :paused (:flush-dom metadata on an event has caused a temporary pause in processing)
[:paused :add-event] [:paused #(-add-event this arg)]
[:paused :resume] [:running #(-resume this)]
(throw (ex-info (str "re-frame: router state transition not found. " fsm-state " " trigger)
{:fsm-state fsm-state, :trigger trigger})))]
(throw (ex-info (str "re-frame: router state transition not found. " fsm-state " " trigger)
{:fsm-state fsm-state, :trigger trigger})))]
;; The "case" above computed both the new FSM state, and the action. Now, make it happen.
(set! fsm-state new-fsm-state)
(when action-fn (action-fn))))
;; The "case" above computed both the new FSM state, and the action. Now, make it happen.
(trace/merge-trace! {:operation [fsm-state trigger]
:tags {:current-state fsm-state
:new-state new-fsm-state}})
(set! fsm-state new-fsm-state)
(when action-fn (action-fn)))))
(-add-event
[_ event]

View File

@ -1,11 +1,11 @@
(ns re-frame.subs
(:require
[re-frame.db :refer [app-db]]
[re-frame.interop :refer [add-on-dispose! debug-enabled? make-reaction ratom? deref?]]
[re-frame.interop :refer [add-on-dispose! debug-enabled? make-reaction ratom? deref? dispose!]]
[re-frame.loggers :refer [console]]
[re-frame.utils :refer [first-in-vector]]
[re-frame.registrar :refer [get-handler clear-handlers register-handler]]))
[re-frame.utils :refer [first-in-vector reagent-id]]
[re-frame.registrar :refer [get-handler clear-handlers register-handler]]
[re-frame.trace :as trace :include-macros true]))
(def kind :sub)
(assert (re-frame.registrar/kinds kind))
@ -17,11 +17,29 @@
;; Two subscriptions are "equal" if their query vectors test "=".
(def query->reaction (atom {}))
(defn clear-subscription-cache!
"Runs on-dispose for all subscriptions we have in the subscription cache.
Used to force recreation of new subscriptions. Should only be necessary
in development.
The on-dispose functions for the subscriptions will remove themselves from the
cache.
Useful when reloading Figwheel code after a React exception, as React components
aren't cleaned up properly. This means subscriptions on-dispose isn't run when the
components are destroyed. If a bad subscription caused your exception, then you
can't fix it without reloading your browser."
[]
(doseq [[k rxn] @query->reaction]
(dispose! rxn))
(if (not-empty @query->reaction)
(console :warn "Subscription cache should be empty after clearing it.")))
(defn clear-all-handlers!
"Unregisters all existing subscription handlers"
[]
(clear-handlers kind)
(reset! query->reaction {}))
(clear-subscription-cache!))
(defn cache-and-return
"cache the reaction r"
@ -29,9 +47,14 @@
(let [cache-key [query-v dynv]]
;; when this reaction is no longer being used, remove it from the cache
(add-on-dispose! r #(do (swap! query->reaction dissoc cache-key)
#_(console :log "Removing subscription:" cache-key)))
(trace/with-trace {:operation (first-in-vector query-v)
:op-type :sub/dispose
:tags {:query-v query-v
:reaction (reagent-id r)}}
nil)))
;; cache this reaction, so it can be used to deduplicate other, later "=" subscriptions
(swap! query->reaction assoc cache-key r)
(trace/merge-trace! {:tags {:reaction (reagent-id r)}})
r)) ;; return the actual reaction
(defn cache-lookup
@ -46,33 +69,48 @@
(defn subscribe
"Returns a Reagent/reaction which contains a computation"
([query-v]
(trace/with-trace {:operation (first-in-vector query-v)
:op-type :sub/create
:tags {:query-v query-v}}
(if-let [cached (cache-lookup query-v)]
(do ;(console :log "Using cached subscription: " query-v)
(do
(trace/merge-trace! {:tags {:cached? true
:reaction (reagent-id cached)}})
cached)
(let [query-id (first-in-vector query-v)
handler-fn (get-handler kind query-id)]
;(console :log "Subscription created: " query-v)
(if-not handler-fn
(console :error (str "re-frame: no subscription handler registered for: \"" query-id "\". Returning a nil subscription.")))
(cache-and-return query-v [] (handler-fn app-db query-v)))))
(trace/merge-trace! {:tags {:cached? false}})
(if (nil? handler-fn)
(do (trace/merge-trace! {:error true})
(console :error (str "re-frame: no subscription handler registered for: \"" query-id "\". Returning a nil subscription.")))
(cache-and-return query-v [] (handler-fn app-db query-v)))))))
([v dynv]
(trace/with-trace {:operation (first-in-vector v)
:op-type :sub/create
:tags {:query-v v
:dyn-v dynv}}
(if-let [cached (cache-lookup v dynv)]
(do ;(console :log "Using cached subscription: " v " and " dynv)
(do
(trace/merge-trace! {:tags {:cached? true
:reaction (reagent-id cached)}})
cached)
(let [query-id (first-in-vector v)
handler-fn (get-handler kind query-id)]
(trace/merge-trace! {:tags {:cached? false}})
(when debug-enabled?
(when-let [not-reactive (not-empty (remove ratom? dynv))]
(console :warn "re-frame: your subscription's dynamic parameters that don't implement IReactiveAtom:" not-reactive)))
(if (nil? handler-fn)
(when-not handler-fn
(trace/merge-trace! {:error true})
(console :error (str "re-frame: no subscription handler registered for: \"" query-id "\". Returning a nil subscription."))
(let [dyn-vals (make-reaction (fn [] (mapv deref dynv)))
sub (make-reaction (fn [] (handler-fn app-db v @dyn-vals)))]
sub (make-reaction (fn [] (handler-fn app-db v @dyn-vals)))]
;; handler-fn returns a reaction which is then wrapped in the sub reaction
;; need to double deref it to get to the actual value.
;(console :log "Subscription created: " v dynv)
(cache-and-return v dynv (make-reaction (fn [] @@sub)))))))))
(cache-and-return v dynv (make-reaction (fn [] @@sub))))))))))
;; -- reg-sub -----------------------------------------------------------------
@ -87,11 +125,13 @@
(defn- deref-input-signals
[signals query-id]
(cond
(let [signals (cond
(sequential? signals) (map deref signals)
(map? signals) (map-vals deref signals)
(deref? signals) @signals
:else (console :error "re-frame: in the reg-sub for " query-id ", the input-signals function returns: " signals)))
(map? signals) (map-vals deref signals)
(deref? signals) @signals
:else (console :error "re-frame: in the reg-sub for " query-id ", the input-signals function returns: " signals))]
(trace/merge-trace! {:tags {:input-signals (map reagent-id signals)}})
signals))
(defn reg-sub
@ -160,10 +200,26 @@
query-id
(fn subs-handler-fn
([db query-vec]
(let [subscriptions (inputs-fn query-vec)]
(make-reaction
(fn [] (computation-fn (deref-input-signals subscriptions query-id) query-vec)))))
(let [subscriptions (inputs-fn query-vec)
reaction-id (atom nil)
reaction (make-reaction
(fn [] (trace/with-trace {:operation (first-in-vector query-vec)
:op-type :sub/run
:tags {:query-v query-vec
:reaction @reaction-id}}
(computation-fn (deref-input-signals subscriptions query-id) query-vec))))]
(reset! reaction-id (reagent-id reaction))
reaction))
([db query-vec dyn-vec]
(let [subscriptions (inputs-fn query-vec dyn-vec)]
(make-reaction
(fn [] (computation-fn (deref-input-signals subscriptions query-id) query-vec dyn-vec)))))))))
(let [subscriptions (inputs-fn query-vec dyn-vec)
reaction-id (atom nil)
reaction (make-reaction
(fn []
(trace/with-trace {:operation (first-in-vector query-vec)
:op-type :sub/run
:tags {:query-v query-vec
:dyn-v dyn-vec
:reaction @reaction-id}}
(computation-fn (deref-input-signals subscriptions query-id) query-vec dyn-vec))))]
(reset! reaction-id (reagent-id reaction))
reaction))))))

76
src/re_frame/trace.cljc Normal file
View File

@ -0,0 +1,76 @@
(ns re-frame.trace
"Tracing for re-frame.
Alpha quality, subject to change/break at any time."
(:require [re-frame.interop :as interop]
[re-frame.loggers :refer [console]]))
(def id (atom 0))
(def ^:dynamic *current-trace* nil)
(defn reset-tracing! []
(reset! id 0))
#?(:cljs (goog-define trace-enabled? false)
:clj (def ^boolean trace-enabled? false))
(defn ^boolean is-trace-enabled?
"See https://groups.google.com/d/msg/clojurescript/jk43kmYiMhA/IHglVr_TPdgJ for more details"
[]
trace-enabled?)
(def trace-cbs (atom {}))
(defn register-trace-cb
"Registers a tracing callback function which will receive a collection of one or more traces.
Will replace an existing callback function if it shares the same key."
[key f]
(swap! trace-cbs assoc key f))
(defn remove-trace-cb [key]
(swap! trace-cbs dissoc key)
nil)
(defn next-id [] (swap! id inc))
(defn start-trace [{:keys [operation op-type tags child-of]}]
{:id (next-id)
:operation operation
:op-type op-type
:tags tags
:child-of (or child-of (:id *current-trace*))
:start (interop/now)})
#?(:clj (defmacro finish-trace [trace]
`(when (is-trace-enabled?)
(let [end# (interop/now)
duration# (- end# (:start ~trace))]
(doseq [[k# cb#] @trace-cbs]
(try (cb# [(assoc ~trace
:duration duration#
:end (interop/now))])
#?(:clj (catch Exception e#
(console :error "Error thrown from trace cb" k# "while storing" ~trace e#)))
#?(:cljs (catch :default e#
(console :error "Error thrown from trace cb" k# "while storing" ~trace e#)))))))))
#?(:clj (defmacro with-trace
"Create a trace inside the scope of the with-trace macro
Common keys for trace-opts
:op-type - what kind of operation is this? e.g. :sub/create, :render.
:operation - identifier for the operation, for an subscription it would be the subscription keyword
tags - a map of arbitrary kv pairs"
[{:keys [operation op-type tags child-of] :as trace-opts} & body]
`(if (is-trace-enabled?)
(binding [*current-trace* (start-trace ~trace-opts)]
(try ~@body
(finally (finish-trace *current-trace*))))
(do ~@body))))
#?(:clj (defmacro merge-trace! [m]
;; Overwrite keys in tags, and all top level keys.
`(when (is-trace-enabled?)
(let [new-trace# (-> (update *current-trace* :tags merge (:tags ~m))
(merge (dissoc ~m :tags)))]
(set! *current-trace* new-trace#))
nil)))

View File

@ -1,6 +1,7 @@
(ns re-frame.utils
(:require
[re-frame.loggers :refer [console]]))
[re-frame.loggers :refer [console]]
[reagent.ratom :as ratom]))
(defn first-in-vector
@ -9,3 +10,15 @@
(first v)
(console :error "re-frame: expected a vector, but got:" v)))
(defn reagent-id
"Produces an id for reactive Reagent values
e.g. reactions, ratoms, cursors."
[reactive-val]
#?(:cljs (when (implements? ratom/IReactiveAtom reactive-val)
(str (condp instance? reactive-val
ratom/RAtom "ra"
ratom/RCursor "rc"
ratom/Reaction "rx"
ratom/Track "tr"
"other")
(hash reactive-val)))))

View File

@ -1,15 +1,15 @@
(ns re-frame.subs-test
(:require [cljs.test :refer-macros [is deftest]]
(:require [cljs.test :as test :refer-macros [is deftest]]
[reagent.ratom :refer-macros [reaction]]
[re-frame.subs :as subs]
[re-frame.db :as db]
[re-frame.core :as re-frame]))
(test/use-fixtures :each {:before (fn [] (subs/clear-all-handlers!))})
;=====test basic subscriptions ======
(deftest test-reg-sub
(subs/clear-all-handlers!)
(re-frame/reg-sub-raw
:test-sub
(fn [db [_]] (reaction (deref db))))
@ -20,8 +20,6 @@
(is (= 1 @test-sub))))
(deftest test-chained-subs
(subs/clear-all-handlers!)
(re-frame/reg-sub-raw
:a-sub
(fn [db [_]] (reaction (:a @db))))
@ -44,8 +42,6 @@
(is (= {:a 1 :b 3} @test-sub))))
(deftest test-sub-parameters
(subs/clear-all-handlers!)
(re-frame/reg-sub-raw
:test-sub
(fn [db [_ b]] (reaction [(:a @db) b])))
@ -56,8 +52,6 @@
(deftest test-sub-chained-parameters
(subs/clear-all-handlers!)
(re-frame/reg-sub-raw
:a-sub
(fn [db [_ a]] (reaction [(:a @db) a])))
@ -77,12 +71,13 @@
(reset! db/app-db {:a 1 :b 2})
(is (= {:a [1 :c], :b [2 :c]} @test-sub))))
(deftest test-nonexistent-sub
(is (nil? (re-frame/subscribe [:non-existence]))))
;============== test cached-subs ================
(def side-effect-atom (atom 0))
(deftest test-cached-subscriptions
(subs/clear-all-handlers!)
(reset! side-effect-atom 0)
(re-frame/reg-sub-raw
@ -106,8 +101,6 @@
;============== test register-pure macros ================
(deftest test-reg-sub-macro
(subs/clear-all-handlers!)
(subs/reg-sub
:test-sub
(fn [db [_]] db))
@ -118,8 +111,6 @@
(is (= 1 @test-sub))))
(deftest test-reg-sub-macro-singleton
(subs/clear-all-handlers!)
(subs/reg-sub
:a-sub
(fn [db [_]] (:a db)))
@ -138,8 +129,6 @@
(is (= {:a 1} @test-sub))))
(deftest test-reg-sub-macro-vector
(subs/clear-all-handlers!)
(subs/reg-sub
:a-sub
(fn [db [_]] (:a db)))
@ -163,8 +152,6 @@
(is (= {:a 1 :b 3} @test-sub))))
(deftest test-reg-sub-macro-map
(subs/clear-all-handlers!)
(subs/reg-sub
:a-sub
(fn [db [_]] (:a db)))
@ -188,8 +175,6 @@
(is (= {:a 1 :b 3} @test-sub))))
(deftest test-sub-macro-parameters
(subs/clear-all-handlers!)
(subs/reg-sub
:test-sub
(fn [db [_ b]] [(:a db) b]))
@ -199,8 +184,6 @@
(is (= [1 :c] @test-sub))))
(deftest test-sub-macros-chained-parameters
(subs/clear-all-handlers!)
(subs/reg-sub
:a-sub
(fn [db [_ a]] [(:a db) a]))
@ -222,8 +205,6 @@
(deftest test-sub-macros-<-
"test the syntactial sugar"
(subs/clear-all-handlers!)
(subs/reg-sub
:a-sub
(fn [db [_]] (:a db)))
@ -239,8 +220,6 @@
(deftest test-sub-macros-chained-parameters-<-
"test the syntactial sugar"
(subs/clear-all-handlers!)
(subs/reg-sub
:a-sub
(fn [db [_]] (:a db)))
@ -260,7 +239,6 @@
(is (= {:a 1 :b 2} @test-sub) )))
(deftest test-registering-subs-doesnt-create-subscription
(subs/clear-all-handlers!)
(let [sub-called? (atom false)]
(with-redefs [subs/subscribe (fn [& args] (reset! sub-called? true))]
(subs/reg-sub

View File

@ -6,7 +6,8 @@
;; Test Namespaces -------------------------------
[re-frame.interceptor-test]
[re-frame.subs-test]
[re-frame.fx-test]))
[re-frame.fx-test]
[re-frame.trace-test]))
(enable-console-print!)
@ -19,7 +20,8 @@
(cljs-test/run-tests
're-frame.interceptor-test
're-frame.subs-test
're-frame.fx-test))
're-frame.fx-test
're-frame.trace-test))
;; ---- KARMA -----------------------------------------------------------------
@ -28,4 +30,5 @@
karma
're-frame.interceptor-test
're-frame.subs-test
're-frame.fx-test))
're-frame.fx-test
're-frame.trace-test))

View File

@ -0,0 +1,34 @@
(ns re-frame.trace-test
(:require [cljs.test :as test :refer-macros [is deftest]]
[re-frame.trace :as trace :include-macros true]
[re-frame.core :as rf]))
(def test-traces (atom []))
(test/use-fixtures :once {:before (fn []
(trace/register-trace-cb :test
(fn [traces]
(doseq [trace traces]
(swap! test-traces conj trace)))))
:after (fn []
(trace/remove-trace-cb :test))})
(test/use-fixtures :each {:before (fn []
(reset! test-traces [])
(trace/reset-tracing!))})
; Disabled, as goog-define doesn't work in optimizations :whitespace
;(deftest trace-cb-test
; (trace/with-trace {:operation :test1
; :op-type :test})
; (is (= 1 (count @test-traces)))
; (is (= (select-keys (first @test-traces) [:id :operation :op-type :tags])
; {:id 1 :operation :test1 :op-type :test :tags nil})))
;
;(enable-console-print!)
;
;(deftest sub-trace-test
; (rf/subscribe [:non-existence])
; (is (= 1 (count @test-traces)))
; (is (= (select-keys (first @test-traces) [:id :operation :op-type :error])
; {:id 1 :op-type :sub/create :operation :non-existence :error true})))