{"id":15286906,"url":"https://github.com/sansarip/peanuts","last_synced_at":"2025-04-13T03:57:08.295Z","repository":{"id":42636510,"uuid":"268426528","full_name":"sansarip/peanuts","owner":"sansarip","description":"Packing peanuts for Reagent/Re-frame components","archived":false,"fork":false,"pushed_at":"2023-03-06T03:51:21.000Z","size":6294,"stargazers_count":2,"open_issues_count":5,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-13T03:57:02.404Z","etag":null,"topics":["clojure","clojurescript","re-frame","reagent"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sansarip.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-06-01T04:40:34.000Z","updated_at":"2021-09-08T04:50:38.000Z","dependencies_parsed_at":"2023-02-08T03:46:31.552Z","dependency_job_id":null,"html_url":"https://github.com/sansarip/peanuts","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sansarip%2Fpeanuts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sansarip%2Fpeanuts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sansarip%2Fpeanuts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sansarip%2Fpeanuts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sansarip","download_url":"https://codeload.github.com/sansarip/peanuts/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248661707,"owners_count":21141450,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","clojurescript","re-frame","reagent"],"created_at":"2024-09-30T15:18:55.247Z","updated_at":"2025-04-13T03:57:08.270Z","avatar_url":"https://github.com/sansarip.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"https://i.gyazo.com/6b5da1a1dfaf5bbdc5e8e478431c6281.png\" alt=\"Peanuts Logo\" title=\"Peanuts\" align=\"right\" width=\"250px\" /\u003e\n\n# Peanuts\n\n[![Clojars Project](https://img.shields.io/clojars/v/peanuts.svg)](https://clojars.org/peanuts) [![Build Status](https://travis-ci.org/sansarip/peanuts.svg?branch=main)](https://travis-ci.org/sansarip/peanuts)\n\n\u003e Packing peanuts for decoupling Reagent Form-1 components from Re-frame subscriptions\n\n[deps.edn](https://clojure.org/reference/deps_and_cli)\n```clojure\npeanuts/peanuts {:mvn/version \"0.7.2\"}\n```\n\n[Leiningen](https://github.com/technomancy/leiningen)\n```clojure\n[peanuts \"0.7.2\"]\n```\n\n## ToC\n\n1. [Rationale](#rationale)\n2. [Usage](#usage)\n    1. [Interactive devcards](https://sansarip.github.io/peanuts/#!/peanuts.cards.main)\n    2. [fnc macro](#fnc)\n    3. [defnc macro](#defnc)\n    4. [Options](#options)\n        1. [Redlisting Args](#redlisting-args)\n        2. [Greenlisting Args](#greenlisting-args)\n3. [Known Limitations](#limitations)\n\n## Rationale \u003ca name=\"rationale\"\u003e\u003c/a\u003e\n\nThis bit is pretty opinionated, but I dislike using the below structure to define Reagent Form-1 components.\n\n```clojure\n;; Method A\n\n(defn my-component []\n  (let [... bunch of re-frame subscriptions here ...]\n    [:div \"hiccup stuff\"]))\n```\n\nThe problem with the above example is that functions defined as such become impure, heavily dependent on the Re-frame subscriptions bound in their let-forms - coupling the components and the subscriptions. This makes it harder to create a library of components that you can share between projects, and it makes the components harder to test.\n\nI prefer something like this...\n\n```clojure\n;; Method B\n\n(defn my-component [{:keys [... args ...]}]\n  [:div \"hiccup stuff\"])\n```\n\nIn my preferred method (Method B) the subscriptions would happen outside of the components (in a root component/view), and the data would just simply be passed in. The problem with my preferred method is that it can noticeably affect performance when you have nested components - due to Re-frame subscription mumbo-jumbo.\n\nEnter Peanuts. Peanuts component macros are intended to wrap components implemented like Method B, turning them into components that behave like Method A without the performance drawback. The component will use any args passed in as is _or_ subscribe to them if the args are valid subscription ids/vectors!\n\nI've utilize this simple library in production to great extents, and it has really scratched an itch for me!\n\n## Usage \u003ca name=\"usage\"\u003e\u003c/a\u003e\n\n[![Image from Gyazo](https://i.gyazo.com/5b43a839b7f7f8af7e547eef44efb56c.gif)](https://gyazo.com/5b43a839b7f7f8af7e547eef44efb56c)\n\n*An example of wrapping an existing Form-1 component*\n\nIt goes without saying that you should have [re-frame](https://github.com/Day8/re-frame) as a project dependency. You may also need to require it in the namespace(s) you use Peanuts. \n\nThe main ways to use peanut components are the `fnc` and `defnc` macros. \nFor documentation on the older `defc` and `fc` macros, see [the README here](https://github.com/sansarip/peanuts/tree/7b9718519760c254942c2df2eeb5aa52e4ec2181)\n\n#### fnc \u003ca name=\"fnc\"\u003e\u003c/a\u003e\n\nSimilar to `fn`\n\n```clojure\n(ns my-ns\n  (:require [peanuts.core :refer [defnc]]))\n\n(def foo (fnc [n] [:p (str \"Hello, \" n \"!\")]))\n```\n\n#### defnc \u003ca name=\"defnc\"\u003e\u003c/a\u003e\n\nSimilar to `defn`\n\n```clojure\n(ns my-ns \n  (:require [peanuts.core :refer [defnc]]))\n\n(defnc foo [n]\n  [:p (str \"Hello, \" n \"!\")])\n```\n\nSee this [little blurb](https://cursive-ide.com/userguide/macros.html) if you wish to resolve `defnc` as a `defn` and `fnc` as an `fn` in IntelliJ with Cursive!\n\nYou can also import the clj-kondo config like so:\n\n```clojure\n{:config-paths [\"peanuts/peanuts\"]}\n```\n\n### Options \u003ca name=\"options\"\u003e\u003c/a\u003e\n\nBoth `fnc` and `defnc` accept an optional map as an argument that can dictate certain options explained below. \nIn the case of `defnc` the map will also be applied as metadata on the defined name. \nYou can also pass the options map as the second - or third if there's a docstring - argument.\n\nThere are older supported options available that you can [read about here](https://github.com/sansarip/peanuts/tree/5499859a2a00d37454256312b1d784c80ddb6587#options). But, I'm only supporting them for backward compatibility's sake and would advise _against_ using those options!\n\n#### Redlisting Args \u003ca name=\"redlisting-args\"\u003e\u003c/a\u003e\n\nThe `:redlist` options are there for instances where you'd want certain args that coincide with valid subscription-identifiers/vectors to pass through without being rebound to their respective subscription values.\n\n```clojure\n(defnc foo\n  [adj n]\n  {:redlist [adj]}\n  [:p (str \"Hello, \" adj \" \" n \"!\")])\n\n;; Or\n\n(defnc foo\n   [^:redlist adj n]\n   [:p (str \"Hello, \" adj \" \" n \"!\")])\n```\n\nIn the above examples, the `adj` parameter will always be redlisted/excluded from being rebound to subscription values.\n\nYou can also redlist args with metadata when calling the function/component.\n\n```clojure\n(defnc foo\n   [adj n]\n   [:p (str \"Hello, \" adj \" \" n \"!\")])\n\n;; You can also use :rl instead of :redlist\n[foo ^:redlist [:my-adj] :my-name]\n```\n\nOne thing to note with the above example though is that although the `adj` arg will be exempt from being rebound to a subscription value if a valid subscription identifier/vector is passed in, it won't change the amount of code the `defnc` macro emits. In contrast, omitting parameters via the `:redlist` (or `:greenlist`) options does result in less code being emitted - if you care about that!\n\nIn addition, you can redlist an arg _once_ with metadata.\nThis can be handy when you want to pass down a subscription-id/vector to a nested\npeanuts component. This is a similar behavior to using the `:redlist` option\nin the peanuts component definition.\n\n```clojure\n(defnc bar\n       [adj n]\n       ;; adj will be rebound to subscription value\n       [:p (str n \" is an \" adj \" name!\")])\n\n(defnc foo\n       [adj n]\n       [:\u003c\u003e \n        ;; adj will be used as is e.g. [:my-adj]\n        [:p (str \"Hello, \" adj \" \" n \"!\")]\n        [bar adj n]])\n\n;; You can also use :rl1 instead of redlist1\n[foo ^:redlist1 [:my-adj] :my-name]\n```\n\n#### Greenlisting Args \u003ca name=\"greenlisting-args\"\u003e\u003c/a\u003e\n\nAn alternative to the `:redlist` option is the `:greenlist` option. If the `:greenlist` option is specified, then only those specified parameters will be candidates for being rebound subscription values.\n\n```clojure\n(defnc foo\n  [adj n]\n  {:greenlist [n]}\n  [:p (str \"Hello, \" adj \" \" n \"!\")])\n```\n\nThe above example is equivalent to the example in the [Redlisting Args section](#redlisting-args). In the example above, only the greenlisted `n` parameter can be rebound to a subscription value. \nOne thing to note is that the `:redlist` option always takes precedence over the `:greenlist` option in odd cases where both \noptions are defined with conflicting args.\n\n## Known Limitations \u003ca name=\"limitations\"\u003e\u003c/a\u003e\n\nThis library doesn't fully replicate all the bells and whistles of the `defn` macro or the `fn` form. \n\nThere are some known limitations:\n\n* Function overloading isn't supported\n* `fnc` does not support naming e.g. `(fnc d [])` does not work\n* Function constraints e.g.\n\n```clojure\n(defn constrained-sqr [x]\n    {:pre  [(pos? x)]\n     :post [(\u003e % 16), (\u003c % 225)]}\n    (* x x))\n```\n\nIt's not that the above limitations can't be fixed; I just haven't run into a necessary use case yet. If there's demand to fix any of the mentioned limitations, I will do it!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsansarip%2Fpeanuts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsansarip%2Fpeanuts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsansarip%2Fpeanuts/lists"}