Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce pure clojure wrappers for React Context #594

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 15 additions & 3 deletions examples/react-context/src/example/core.cljs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
(ns example.core
(:require [reagent.core :as r]
(:require [reagent.context :as r.context]
[reagent.core :as r]
[reagent.dom :as rdom]
[react :as react]
[goog.object :as gobj]))


(defonce my-context (react/createContext "default"))
(defonce my-context (r.context/make-context "my-context" "default"))

(def Provider (.-Provider my-context))
(def Consumer (.-Consumer my-context))
Expand All @@ -16,6 +16,18 @@
(r/as-element [:div "Context: " (pr-str v)]))])

(defn root []
;; When using the pure Clojure wrappers, the value is passed as is.
[:div
[r.context/provider {:value {:foo :bar} ;; The value here can be anything, does not need to be a map
:context my-context}
[r.context/consumer {:context my-context}
(fn [{:keys [foo]}]
[:div ":foo is a keyword: " (pr-str foo)])]

;; The `with-context` macro cuts away some boilerplate from the above
(r.context/with-context [{:keys [foo]} my-context]
[:div "From the macro: " (pr-str foo)])]]

;; Provider takes props with single property, value
;; :< or adapt-react-class converts the Cljs properties
;; map to JS object for the Provider component.
Expand Down
17 changes: 17 additions & 0 deletions src/reagent/context.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
(ns reagent.context)

(defmacro with-context
"Defines a context consumer component.
First argument should be a binding as in when-let, with the left value being a context identifier.
Remaining arguments will be the render function of the component

(reagent.context/with-context [{:keys [foo bar]} react-context-id]
[:div ...])"
[bindings & body]
(assert (and (vector? bindings)
(= 2 (count bindings)))
"First argument must be a binding vector with 2 elements")
(let [[left right] bindings]
`[consumer {:context ~right}
(fn [~left]
~@body)]))
42 changes: 42 additions & 0 deletions src/reagent/context.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
(ns reagent.context
(:require-macros [reagent.core]
[reagent.context])
(:require [react :as react]
[reagent.core :as r]))

(defn make-context
"Creates a context with the given name and optional default value.
The default value will be used when trying to retrieve the context value with no provider present.
Attempting to retrieve a context value that has no provider, and no default value will result in a crash."
([name]
(let [context (react/createContext)]
(set! (.-displayName context) name)
context))
([name default-value]
(let [context (react/createContext default-value)]
(set! (.-displayName context) name)
context)))

(defn provider
"Provides the value for the given context to descendant components."
[{:keys [context value]} & contents]
(into [:r> (.-Provider context) #js{:value value}]
contents))

(defn consumer
"Retrieves the value for the given context.
render-f must be a reagent render function that will be called with the value.
If there's no provider, will return the default value if it is set, or throw otherwise."
[{:keys [context]} render-f]
;; Use with-let to maintain a stable render function, otherwise the child will
;; remount every time a new prop comes into the parent.
(r/with-let [wrapper-comp
;; Passes through context to component using meta data. See:
;; https://github.com/reagent-project/reagent/blob/ce80585e9aebe0a6df09bda1530773aa512f6103/doc/ReactFeatures.md#context
^{:context-type context}
(fn [render-f]
(let [value (.-context (r/current-component))]
(when (undefined? value)
(throw (js/Error. (str "Missing provider for " (.-displayName context)))))
(render-f value)))]
Comment on lines +38 to +41

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one of the problems with using the Consumer component is that you couldn't deref ratoms safely inside the render fn; they wouldn't setup a listener because they wouldn't have the reactive context. Does this fix that?

[wrapper-comp render-f]))