Skip to content

Latest commit

 

History

History
888 lines (672 loc) · 34.1 KB

DOCS.md

File metadata and controls

888 lines (672 loc) · 34.1 KB

Project Documentation

Step by step

1. Download and Install OBS

OBS (Open Broadcaster Software) is a free and open-source software for video recording and live streaming. To begin:

  1. Visit the OBS official website.
  2. Download the installer for your operating system (Windows, macOS, or Linux).
  3. Follow the installation instructions to set up OBS on your system.

2. Add Video Source

Once OBS is installed, you need to add a video source to start recording:

  1. Open OBS and navigate to the "Sources" panel.
  2. Click the "+" button to add a new source.
  3. Select the type of source you want to add (e.g., Display Capture, Window Capture, Video Capture Device).
  4. Configure the source settings as needed and click "OK".

3. Set Recording Output Folder and Formats

To ensure your recordings are saved in the desired location and format:

  1. Go to the "Settings" menu in OBS.
  2. Navigate to the "Output" tab.
  3. Under the "Recording" section, set the recording path to the folder where you want to save your recordings.
  4. Choose the recording format (e.g., mp4, flv) based on your requirements.

Aero System Config Tool → juxt/aero

The Aero system configuration tool, provided by the juxt/aero library, is a configuration library for Clojure applications. It leverages EDN (Extensible Data Notation) for defining configuration files and provides features to handle various configuration needs like environment-specific settings, type coercion, and more.

Key Features of Aero:

EDN Files

EDN (Extensible Data Notation) is a subset of Clojure used for data interchange. It is often used in configuration files for Clojure applications. To use EDN files in your project:

  1. Create a .edn file for your configuration data.
  2. Use the EDN format to structure your data in a human-readable way.

Multi Profiles

Multi profiles allow you to manage different configuration settings for various environments (e.g., development, testing, production):

  1. Define multiple profiles in your EDN configuration file.
  2. Use a profile key to switch between different sets of configurations.

ENV Keys

ENV keys are used to reference environment variables in your configuration:

  1. Use the #:env prefix in your EDN file to denote environment variables.
  2. Ensure your environment variables are set correctly in your system or deployment environment.

Type Coercion

Type coercion ensures that configuration values are automatically converted to the appropriate data types:

  1. Define the expected types in your EDN file.
  2. Use a library like Aero to handle the type coercion based on the defined schema.

Aero System Config Tool → juxt/aero

Explanation:

The Aero system configuration tool, provided by the juxt/aero library, is a configuration library for Clojure applications. It leverages EDN (Extensible Data Notation) for defining configuration files and provides features to handle various configuration needs like environment-specific settings, type coercion, and more.

Key Features of Aero:

  1. EDN Files:

    • What: Uses EDN format for configuration files, which is a subset of Clojure's syntax designed for data interchange.
    • Why: EDN files are human-readable, easy to write, and support rich data structures.
  2. Multi Profiles:

    • What: Supports defining multiple profiles within a single configuration file.
    • Why: Allows you to manage different configurations for different environments (e.g., development, testing, production) within the same file.
  3. ENV Keys:

    • What: Allows referencing environment variables directly within the configuration file.
    • Why: Enables dynamic configuration based on the environment in which the application is running, enhancing flexibility and security.
  4. Type Coercion:

    • What: Automatically converts configuration values to the appropriate data types based on the schema defined in the configuration.
    • Why: Ensures that configuration values are interpreted correctly as the intended data types, reducing the risk of errors.

Basic Usage Example:

Here is a simple example to illustrate how Aero can be used:

  1. EDN Configuration File (config.edn):

    {
      ;; Define profiles
      :profiles
      {
        :dev {:db-url "jdbc:postgresql://localhost/dev-db"}
        :prod {:db-url "jdbc:postgresql://prod-db-server/prod-db"}
      }
    
      ;; Reference environment variables
      :api-key #:env "API_KEY"
    
      ;; Use type coercion for port number
      :server-port ^:int 8080
    }
  2. Clojure Code to Read Configuration:

    (ns my-app.config
      (:require [aero.core :refer (read-config)]))
    
    (def config (read-config "config.edn"))
    
    ;; Access configuration values
    (def db-url (:db-url config))
    (def api-key (:api-key config))
    (def server-port (:server-port config))

In this example:

  • Multiple profiles (:dev and :prod) are defined, allowing the application to switch between different database URLs based on the environment.
  • The :api-key is retrieved from an environment variable, making the configuration flexible and secure.
  • The :server-port is coerced to an integer type, ensuring the value is correctly interpreted.

Aero simplifies managing configuration in Clojure applications, making it easier to maintain and adapt to different environments and requirements.


Create a New Repository on the Command Line

There are two different ways to create and connect a project to GitHub:

  1. Create a project from scratch using the terminal.
  2. Connect an already existing project to GitHub.

1. How to Create a Project from Scratch

To create a new repository and push it to GitHub from the command line, follow these steps:

  1. Create a README.md file:

    echo "# artchess" >> README.md

    Explanation: This command creates a new file named README.md and adds a line with the text # artchess to it. The README.md file is typically used to provide information about your project.

  2. Initialize a New Git Repository:

    git init

    Explanation: This command initializes a new Git repository in your current directory. It creates a .git directory that contains all the necessary files and structures for version control.

  3. Add README.md to the Repository:

    git add README.md

    Explanation: This command stages the README.md file, marking it for inclusion in the next commit.

  4. Commit the Staged Files:

    git commit -m "first commit"

    Explanation: This command commits the staged files with the message "first commit". Commits are snapshots of your repository at specific points in time.

  5. Create and Switch to the Main Branch:

    git branch -M main

    Explanation: This command creates a new branch named main and switches to it. The -M flag renames the current branch to main.

  6. Add Remote Repository:

    git remote add origin [email protected]:VictorInacio/artchess.git

    Explanation: This command adds a remote repository named origin. The remote repository is hosted on GitHub, and the URL specifies where the repository is located.

  7. Push to the Remote Repository:

    git push -u origin main

    Explanation: This command pushes your local commits to the main branch of the remote repository. The -u flag sets the remote origin as the default upstream for the main branch.


2. How to Connect an Existing Project to a Remote Repository

To connect an existing project to a remote repository, follow these steps:

  1. Initialize a New Git Repository (if not already initialized):

    git init

    Explanation: This command initializes a new Git repository in your current directory if it has not already been initialized.

  2. Stage All Files:

    git add .

    Explanation: This command stages all files in the current directory for inclusion in the next commit.

  3. Commit the Staged Files:

    git commit -m "Initial commit"

    Explanation: This command commits the staged files with the message "Initial commit".

  4. Create and Switch to the Main Branch:

    git branch -M main

    Explanation: This command creates a new branch named main and switches to it. The -M flag renames the current branch to main.

  5. Add Remote Repository:

    git remote add origin [email protected]:VictorInacio/artchess.git

    Explanation: This command adds a remote repository named origin. The remote repository is hosted on GitHub, and the URL specifies where the repository is located.

  6. Push to the Remote Repository:

    git push -u origin main

    Explanation: This command pushes your local commits to the main branch of the remote repository. The -u flag sets the remote origin as the default upstream for the main branch.

Maven Dependency Structure Explanation

In a Clojure project, dependencies are managed using the deps.edn file, which is a data-oriented way of specifying dependencies, unlike Maven's XML format. Here's how to interpret the Maven dependency for org.clojure/java.jdbc:

Dependency Declaration

{:deps
 {org.clojure/java.jdbc {:mvn/version "0.7.12"}}
}
  • :deps: This key in deps.edn specifies the dependencies map where you define all the libraries your project depends on.

  • org.clojure/java.jdbc: This is the coordinate identifier for the dependency. It consists of:

    • Group ID (org.clojure): Indicates the group or organization that maintains the library. In this case, it's Clojure itself (org.clojure).
    • Artifact ID (java.jdbc): Identifies the specific library or artifact within the group. Here, java.jdbc refers to the Clojure library for JDBC (Java Database Connectivity).
  • :mvn/version "0.7.12": This specifies the version of the library you want to use. It follows the Maven versioning syntax, where "0.7.12" is the version number of the java.jdbc library.

How Maven Dependencies Work

  • Group ID: Identifies the organization or group responsible for the library.
  • Artifact ID: Specifies the specific library or module within the group.
  • Version: Specifies the version of the library to use. It follows semantic versioning principles (major.minor.patch).

Importance of Maven in Dependency Management

  • Consistency: Maven provides a standardized way to manage dependencies across projects.
  • Automation: Tools like clj or lein automate the fetching and management of dependencies based on deps.edn specifications.
  • Compatibility: Ensures that compatible versions of libraries are used, reducing compatibility issues.

Summary

The Maven dependency declaration org.clojure/java.jdbc {:mvn/version "0.7.12"} in your deps.edn file specifies that your Clojure project requires version 0.7.12 of the java.jdbc library from the org.clojure group. Maven coordinates (group ID, artifact ID, version) are crucial for specifying and managing dependencies effectively in Clojure projects.

Using com.github.seancorfield/honeysql for Database Queries

Explanation

com.github.seancorfield/honeysql is a library in Clojure that facilitates building SQL queries programmatically using Clojure data structures instead of strings. This approach enhances code readability, maintainability, and safety against SQL injection attacks.

Maven Dependency

Add honeysql to your deps.edn file:

{:deps
 {com.github.seancorfield/honeysql {:mvn/version "0.6.2"}}
}

Basic Examples

Let's explore how to use honeysql for common database operations.

1. Select Query
(ns your-app.db
  (:require [honeysql.core :as sql]
            [honeysql.helpers :refer [select]]))

;; Construct a select query
(def select-query
  (-> (select :*)
      (sql/from :users)
      (sql/where [:= :status "active"])
      sql/format))

(println "Generated SQL query:" select-query)
  • Explanation:
    • select :* constructs a SELECT * statement.
    • sql/from :users specifies the table users.
    • sql/where [:= :status "active"] adds a WHERE status = 'active' condition.
    • sql/format converts the query data structure into a SQL string.
2. Insert Query
(ns your-app.db
  (:require [honeysql.core :as sql]
            [honeysql.helpers :refer [insert]]))

;; Construct an insert query
(def insert-query
  (-> (insert-into :users)
      (sql/values {:name "Alice" :age 30}))
      sql/format))

(println "Generated SQL query:" insert-query)
  • Explanation:
    • insert-into :users specifies the INSERT INTO users statement.
    • sql/values {:name "Alice" :age 30} specifies the values to insert into the table.
    • sql/format converts the query data structure into a SQL string.
3. Update Query
(ns your-app.db
  (:require [honeysql.core :as sql]
            [honeysql.helpers :refer [update]]))

;; Construct an update query
(def update-query
  (-> (update :users)
      (sql/set-fields {:age 31})
      (sql/where [:= :name "Alice"]))
      sql/format))

(println "Generated SQL query:" update-query)
  • Explanation:
    • update :users constructs the UPDATE users statement.
    • sql/set-fields {:age 31} specifies the fields to update (age set to 31).
    • sql/where [:= :name "Alice"] adds a WHERE name = 'Alice' condition.
    • sql/format converts the query data structure into a SQL string.

Where code found in project, structure example

  1. src Directory:

    • src: The main source code directory where the application's code is stored.
  2. resources Directory:

    • resources: A directory for storing non-code resources such as configuration files, static files, and other assets.

In many projects, you might see paths defined like this:

  • src: Contains all the source code files.
  • resources: Contains all the resource files needed by the application.

Example structure:

my-project/
├── src/
│   └── main/
│       ├── app/
│       │   └── main.py
│       └── utils/
│           └── helper.py
└── resources/
    └── config/
        └── settings.yaml

This structure helps in maintaining a clean separation between code and resources, making the project easier to navigate and manage.

Adding Frontend in Our Applications

  1. Create public Directory in resources:

    • Step: Create a public directory inside the resources directory.
    • Purpose: To store frontend-related files, such as HTML, CSS, and JavaScript.
  2. Create index.html:

    • Step: Inside the public directory, create an index.html file.
    • Important Detail: Ensure that the <script src="js/main.js"></script> tag correctly points to the main.js file path. The main.js file will be generated by shadow-cljs during the build process. This is configured in the shadow-cljs settings with the :output-dir option, which specifies that the compiled JavaScript files will be placed in the resources/public/js directory.
    • Purpose: This file serves as the entry point for the frontend application.
  3. Create and Set Up Shadow-cljs.edn File and Settings:

    • Step: In the root directory of your project, create a shadow-cljs.edn file.
    • Purpose: To configure the shadow-cljs build tool for your project.
    • Detail:
      • Build Configuration: Define a build configuration for your app. This includes specifying the target platform, entry points, and output directory.
      • Output Directory: Ensure that the :output-dir option in the build configuration matches the path specified in the <script> tag of your index.html file.
      • Entry Point: The :entries option in the :modules section specifies the namespace that serves as the entry point for the application. This should match the namespace where your main initialization function is defined.
      • Initialization Function: The :init-fn option specifies the function to call to initialize the application. This should be a fully qualified symbol pointing to your initialization function.
  4. Create app Directory in src:

    • Step: Inside the src directory, create an app directory to hold frontend code.
    • File: Create a core.cljs file and add the app component as the initial page.
    • Purpose: To keep frontend code organized and separate from backend code.
  5. Create shadow-cljs.edn File:

    • Step: In the root directory of your project, create a shadow-cljs.edn file.
    • Purpose: To configure the shadow-cljs build tool for your project.

shadow-cljs.edn Configuration - Keywords - Details

:deps

  • Purpose: Indicates that the project is using a dependencies management system, typically deps.edn.
  • Usage: { :deps true } enables shadow-cljs to use dependencies from deps.edn.

:nrepl

  • Purpose: Configures the Network REPL (nREPL) for Clojure development.
  • Options:
    • :port: Specifies the port number on which the nREPL server listens.
  • Example:
    :nrepl {:port 9000}

:builds

  • Purpose: Defines the build configurations for different targets in the project.
  • Usage: Each build configuration is specified under a unique key (e.g., :app).
Build Configuration Keywords
  • :target

    • Purpose: Specifies the target environment for the build.
    • Common Values:
      • :browser: For browser-based applications.
      • :node-script: For Node.js scripts.
    • Example:
      :target :browser
  • :modules

    • Purpose: Defines the JavaScript modules to be generated by the build.
    • Options:
      • :main: Specifies the entry point module.
      • :entries: A list of namespaces to include in the module.
      • :init-fn: The initialization function to be called when the module is loaded.
    • Example:
      :modules {:main {:entries [app.core]
                       :init-fn app.core/init}}
  • :output-dir

    • Purpose: Specifies the directory where the compiled JavaScript files will be output.
    • Example:
      :output-dir "resources/public/js"
  • :devtools

    • Purpose: Configures development tools and preloads.
    • Options:
      • :preloads: A list of namespaces to preload during development.
    • Example:
      :devtools {:preloads [devtools.preload day8.re-frame-10x.preload]}
  • :compiler-options

    • Purpose: Customizes the ClojureScript compiler options.
    • Options:
      • :closure-defines: Defines constants for the Google Closure compiler.
      • Example:
        :compiler-options {:closure-defines {goog.DEBUG true
                                             re-frame.trace.trace-enabled? true
                                             day8.re-frame.tracing.trace-enabled? true}}
  • :dev

    • Purpose: Contains configurations specific to the development environment.
    • Options:
      • :compiler-options: Additional compiler options for development mode.
    • Example:
      :dev {:compiler-options {:closure-defines {re-frame.trace.trace-enabled? true
                                                 day8.re-frame.tracing.trace-enabled? true}}}
  • :release

    • Purpose: Contains configurations specific to the release (production) environment.
    • Options:
      • :build-options: Additional build options for release mode.
    • Example:
      :release {:build-options {:ns-aliases {day8.re-frame.tracing day8.re-frame.tracing-stubs}}}

Full Example with Explanations

{
  :deps   true     ;; Enables using dependencies from deps.edn
  :nrepl  {:port 9000}   ;; Configures nREPL to listen on port 9000

  :builds {:app { 
                :target           :browser     ;; Specifies that the target environment is the browser
                :modules          {:main {:entries [app.core]
                                          :init-fn app.core/init}} ;; Specifies the entry point module and init function
                :output-dir       "resources/public/js"  ;; Directory where compiled JS files are output
                :devtools         {:preloads [devtools.preload day8.re-frame-10x.preload]}  ;; Preloads namespaces for development tools

                :compiler-options {:closure-defines {goog.DEBUG true
                                                     re-frame.trace.trace-enabled? true
                                                     day8.re-frame.tracing.trace-enabled? true}}  ;; Defines constants for the Closure compiler

                :dev              {:compiler-options {:closure-defines {re-frame.trace.trace-enabled? true
                                                                        day8.re-frame.tracing.trace-enabled? true}}}  ;; Development-specific compiler options

                :release          {:build-options {:ns-aliases {day8.re-frame.tracing day8.re-frame.tracing-stubs}}}  ;; Release-specific build options
               }
          }
}

Key Points to Remember:

  • :output-dir must match the directory specified in your index.html file’s <script src="js/main.js"></script> tag. This ensures the generated main.js file is correctly linked.
  • :init-fn must point to the correct initialization function in your ClojureScript code. This function is responsible for rendering your frontend application.

  1. Add Dependencies in deps.edn:

    • Step: Update the deps.edn file with necessary dependencies.
    • Purpose: To manage project dependencies for both backend and frontend.
  2. Create package.json and Add Dependencies:

    • Step: In the root directory of your project, create a package.json file.
    • Content: Add the necessary dependencies and scripts.
    • Purpose: To manage JavaScript dependencies and scripts for building the frontend.
  3. Install Dependencies:

    • Step: Run yarn install to install the dependencies listed in package.json.
    • Purpose: To ensure all JavaScript dependencies are installed.

Example Project Structure:

my-project/
├── src/
│   └── main/
│       └── app/
│           └── core.cljs
├── resources/
│   └── public/
│       └── index.html
│       └── js/
│           └── main.js
├── shadow-cljs.edn
├── deps.edn
├── package.json
└── yarn.lock

Example Files:

deps.edn:

{:paths   ["src" "resources"]
 :deps    {org.clojure/clojure              {:mvn/version "1.11.1"}
           org.clojure/clojurescript        {:mvn/version "1.11.60"}
           org.clojure/tools.namespace      {:mvn/version "1.4.4"}

           ;; DB
           org.postgresql/postgresql        {:mvn/version "42.2.2"}
           org.clojure/java.jdbc            {:mvn/version "0.7.12"}
           com.github.seancorfield/honeysql {:mvn/version "2.4.1045"}

           ;; HTTP Server
           io.pedestal/pedestal.service     {:mvn/version "0.6.2"}
           io.pedestal/pedestal.route       {:mvn/version "0.6.2"}
           io.pedestal/pedestal.jetty       {:mvn/version "0.6.2"}
           org.clojure/data.json            {:mvn/version "2.4.0"}

           ;; Utils
           aero/aero                        {:mvn/version "1.1.6"}

           ;; FRONTEND
           com.adamrenklint/preo            {:mvn/version "0.1.0"}
           com.pitch/uix.core               {:mvn/version "1.0.1"}
           com.pitch/uix.dom                {:mvn/version "1.0.1"}
           re-frame/re-frame                {:mvn/version "1.3.0"}
           cljs-ajax/cljs-ajax              {:mvn/version "0.8.4"}
           day8.re-frame/tracing            {:mvn/version "0.6.2"}
           cljs-http/cljs-http              {:mvn/version "0.1.48"
                                             :exclusions  [io.grpc/grpc-netty-shaded]}
           reagent-utils/reagent-utils      {:mvn/version "0.3.8"}}
 :aliases {:dev {:extra-paths ["dev"]
                 :extra-deps  {thheller/shadow-cljs       {:mvn/version "2.25.8"}
                               binaryage/devtools         {:mvn/version "1.0.7"}
                               day8.re-frame/tracing      {:mvn/version "0.6.2"}
                               day8.re-frame/re-frame-10x {:mvn/version "1.6.0"}}}}}

shadow-cljs.edn:

{:deps   true
 :nrepl  {:port 9000}
 :builds {:app {:target           :browser
                :modules          {:main {:entries [app.core]
                                          :init-fn app.core/init}}
                :devtools         {:preloads [devtools.preload day8.re-frame-10x.preload]}
  ----->        :output-dir       "resources/public/js"    <------ to create public.jc.main.js
                :compiler-options {:closure-defines {goog.DEBUG                                   true
                                                     re-frame.trace.trace-enabled?                true
                                                     day8.re-frame.tracing.trace-enabled?         true
                                                     "re_frame.trace.trace_enabled_QMARK_"        true
                                                     "day8.re_frame.tracing.trace_enabled_QMARK_" true}}
                :dev              {:compiler-options {:closure-defines {re-frame.trace.trace-enabled? true
                                                                        day8.re-frame.tracing.trace-enabled? true}}}
                :release          {:build-options {:ns-aliases {day8.re-frame.tracing day8.re-frame.tracing-stubs}}}}}}

package.json:

{
  "name": "ArtChessAcademy",
  "scripts": {
    "dev": "shadow-cljs -A:dev watch app",
    "release": "shadow-cljs -A:dev release app"
  },
  "dependencies": {
    "highlight.js": "11.5.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-refresh": "^0.14.0",
    "shadow-cljs": "2.22.9"
  }
}

src/main/app/core.cljs:

(ns app.core
  (:require
    [uix.core :as uix :refer [defui $]]
    [uix.dom]
    ["react" :as react]))

(enable-console-print!)

(defui app []
  (let [[n set-n] (uix/use-state 0)]
    ($ :.flex.flex-col.justify-center.items-center.h-screen
       ($ :img.animate-bounce
          {:src   "https://raw.githubusercontent.com/pitch-io/uix/master/logo.png"
           :width 128})
       ($ :div.text-xl.mb-4 "Hello")
       ($ :div.text-4xl.animate-ping "👋"))))

(defn render []
  (uix.dom/render-root ($ react/StrictMode ($ app))
                       (uix.dom/create-root
                         (js/document.getElementById "root"))))

(defn ^:export init []
  (render))

resources/public/index.html:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Art Chess Academy</title>
</head>
<body>
<div id="root"></div>
<script src="js/main.js"></script>  <------ this file going to create by shadow-cljs by this option: :output-dir "resources/public/js"
</body>
</html>

How Dispatch, Event Handling, Subscription Triangle works

Re-frame Overview

Re-frame is a ClojureScript framework for building web applications using a reactive and functional approach. It follows a unidirectional data flow pattern, which simplifies the management of state and side effects.

Core Concepts

  1. Events: Represent user interactions or other actions that modify the application's state. Events are dispatched and handled by event handlers.

  2. Subscriptions: Provide a way to read data from the application's state and pass it to components. Subscriptions automatically re-calculate and trigger re-renders when the state they depend on changes.

  3. Dispatch: The mechanism used to send events into the application’s event handling system. Dispatching an event triggers a chain of actions that ultimately leads to a state update.

Detailed Lifecycle of Events

1. Defining Events

Events are defined using rf/reg-event-db (for database changes) or rf/reg-event-fx (for side effects). An event handler takes the current state and event arguments, processes them, and returns a new state.

Example: Define an Event Handler to Increment a Counter

(rf/reg-event-db
  :increment-counter
  (fn [db _]
    ;; Increment the counter in the application state
    (update db :counter inc)))
  • :increment-counter is the event name.
  • db is the current application state.
  • update db :counter inc increments the value of the :counter key in the state.

2. Dispatching Events

Dispatching an event sends it to the event handling system, which then invokes the appropriate event handler.

Example: Dispatch an Event on Button Click

(defn increment-button []
  ($ :button {:onClick #(rf/dispatch [:increment-counter])} "Increment"))
  • rf/dispatch sends the :increment-counter event to the event handling system.
  • When the button is clicked, the :increment-counter event is dispatched.

3. Event Handling

The event handler processes the event and updates the application state. The updated state is then used to re-render the UI.

Example Event Handler

(rf/reg-event-db
  :increment-counter
  (fn [db _]
    (update db :counter inc)))
  • This handler updates the :counter key in the state by incrementing its value.

4. State Update

The event handler updates the application's state, which is managed by Re-frame’s database. The state update triggers recalculation of subscriptions that depend on the changed state.

Example Initial State

(def initial-state
  {:counter 0})

5. Subscriptions

Subscriptions are used to read specific parts of the application state and provide them to components. When the state changes, subscriptions are recalculated.

Example Subscription

(rf/reg-sub
  :counter
  (fn [db _]
    (:counter db)))
  • :counter is the subscription name.
  • The subscription function extracts the :counter value from the state.

6. Using Subscriptions in Components

Components use subscriptions to get the data they need to render. When a subscription’s underlying state changes, the component re-renders with the new data.

Example Component Using Subscription

(defui counter-component []
  (let [counter (hooks/use-subscribe [:counter])]
    ($ :div
      ($ :h3 "Counter: " counter))))
  • hooks/use-subscribe is used to get the current value of the :counter subscription.
  • The component re-renders whenever the :counter value changes.

Complete Example: Counter Application

Here's a complete example showing the entire flow:

  1. Event Registration

    (rf/reg-event-db
      :increment-counter
      (fn [db _]
        (update db :counter inc)))
  2. Subscription Registration

    (rf/reg-sub
      :counter
      (fn [db _]
        (:counter db)))
  3. Component Using Subscription

    (defui counter-component []
      (let [counter (hooks/use-subscribe [:counter])]
        ($ :div
          ($ :h3 "Counter: " counter))))
  4. Dispatch Event on User Interaction

    (defn increment-button []
      ($ :button {:onClick #(rf/dispatch [:increment-counter])} "Increment"))

Flow Diagram

Here’s a simplified flow of the dispatch event process:

  1. User Interaction
    (e.g., button click triggers onClick)

  2. Event Dispatch
    (rf/dispatch [:increment-counter])

  3. Event Handling
    (:increment-counter event is processed by its handler)

  4. State Update
    (Application state is updated)

  5. Subscription Recalculation
    (Subscriptions that depend on the updated state are recalculated)

  6. Component Re-rendering
    (UI components are re-rendered with new state data)

Summary

  • Events represent actions and are dispatched to modify state.
  • Subscriptions extract and provide state data to components.
  • Dispatch triggers the entire process, leading to state updates and UI changes.

Purpose of app.hooks Namespace

The app.hooks namespace provides custom hooks that integrate Reagent's reaction system with React’s hooks API. This namespace is crucial for managing and synchronizing state between Re-frame subscriptions and Reagent components in a way that aligns with React's component lifecycle.

Key Functions

  1. use-batched-subscribe:

    • Manages listeners for changes to an atom-like reference (ref). It ensures that all registered listeners are notified when the reference changes and handles cleanup when listeners are removed.
  2. use-sync-external-store:

    • Facilitates subscription to an external store using React’s useSyncExternalStore hook. This ensures that components can stay in sync with external state sources by getting the current snapshot and updating when changes occur.
  3. run-reaction:

    • Manages Reagent reactions by scheduling updates and notifying listeners in batches. This function is used to ensure that changes in Reagent's reaction system are properly handled and applied.
  4. use-reaction:

    • Integrates Reagent reactions with React’s hooks system. It subscribes components to changes in Reagent reactions and returns the current value of the reaction. This allows React components to respond to state changes managed by Reagent.
  5. use-subscribe:

    • Provides a hook for subscribing to Re-frame subscriptions. It creates and manages a subscription to a Re-frame query, ensuring that React components stay updated with the latest state from Re-frame.

Summary

The app.hooks namespace is essential for bridging the gap between Reagent and React. It enables seamless state management and synchronization between Reagent’s reaction system and React’s hooks, allowing for efficient and reactive UI updates.


foo