Skip to content

Latest commit

 

History

History
290 lines (226 loc) · 7.88 KB

File metadata and controls

290 lines (226 loc) · 7.88 KB

Context

Context provides a way to pass data through the component tree without having to pass props down manually at every level. It’s useful for “global” data like themes, user authentication, or localization.

1 The Problem: Prop Drilling

Consider an app where many nested components need access to the current user:

;; Without context: passing user through every level (prop drilling)
(vui-defcomponent app ()
  :state ((user '(:name "Alice" :role "admin")))
  :render
  (vui-component 'layout :user user))

(vui-defcomponent layout (user)
  :render
  (vui-component 'sidebar :user user))

(vui-defcomponent sidebar (user)
  :render
  (vui-component 'user-info :user user))

(vui-defcomponent user-info (user)
  :render
  (vui-text (format "Logged in as: %s" (plist-get user :name))))

This is tedious and makes components harder to reuse. Context solves this.

2 Creating Context - vui-defcontext

(vui-defcontext user nil
  "The currently logged-in user.")

This creates:

  • A context variable named user-context
  • A provider component user-provider
  • A hook use-user to consume the context

2.1 Syntax

(vui-defcontext name default-value &optional docstring)

3 Providing Context

Wrap your component tree with the provider:

(vui-defcomponent app ()
  :state ((user '(:name "Alice" :role "admin")))
  :render
  (user-provider user  ; Provide the context value
                 (vui-component 'layout)))

Everything inside the provider can access the context.

4 Consuming Context - use-NAME

Use the auto-generated hook to access context:

(vui-defcomponent user-info ()
  :render
  (let ((user (use-user)))  ; Get context value
    (vui-text (format "Logged in as: %s"
                      (plist-get user :name)))))

No more prop drilling! The component gets the user directly from context.

5 Complete Example

;; 1. Define the context
(vui-defcontext theme 'light
  "Current UI theme (light or dark).")

;; 2. Create components that use it
(vui-defcomponent themed-text (content)
  :render
  (let ((theme (use-theme)))
    (vui-text content
              :face (if (eq theme 'dark)
                        '(:foreground "white" :background "black")
                      '(:foreground "black" :background "white")))))

(vui-defcomponent theme-toggle ()
  :render
  (let ((theme (use-theme)))
    (vui-button (format "Theme: %s" theme)
                :on-click (lambda ()
                            ;; Note: This won't work! See next section
                            (message "Can't change context from here")))))

;; 3. Provide the context
(vui-defcomponent app ()
  :state ((current-theme 'light))
  :render
  (theme-provider current-theme
                  (vui-fragment
                   (vui-component 'themed-text :content "Hello!")
                   (vui-newline)
                   (vui-button "Toggle theme"
                               :on-click (lambda ()
                                           (vui-set-state
                                            :current-theme
                                            (if (eq current-theme 'light)
                                                'dark
                                              'light)))))))

6 Updating Context

Context values are read-only from consumers. To update context, you have two options:

6.1 Option 1: Provide a Setter in Context

(vui-defcontext user nil)

(vui-defcomponent app ()
  :state ((user '(:name "Alice")))
  :render
  (let ((context-value (list :user user
                             :set-user (lambda (new-user)
                                         (vui-set-state :user new-user)))))
    (user-provider context-value
                   (vui-component 'user-editor))))

(vui-defcomponent user-editor ()
  :render
  (let* ((ctx (use-user))
         (user (plist-get ctx :user))
         (set-user (plist-get ctx :set-user)))
    (vui-fragment
     (vui-text (format "Name: %s" (plist-get user :name)))
     (vui-newline)
     (vui-button "Change name"
                 :on-click (lambda ()
                             (funcall set-user '(:name "Bob")))))))

6.2 Option 2: Lift State to Provider

;; Theme context with setter pattern
(vui-defcontext theme-context nil)

(vui-defcomponent theme-provider-wrapper ()
  :state ((theme 'light))
  :render
  (theme-context-provider
   (list :theme theme
         :toggle (lambda ()
                   (vui-set-state :theme
                                  (if (eq theme 'light) 'dark 'light))))
   children))

(vui-defcomponent theme-consumer ()
  :render
  (let* ((ctx (use-theme-context))
         (theme (plist-get ctx :theme))
         (toggle (plist-get ctx :toggle)))
    (vui-button (format "Theme: %s (click to toggle)" theme)
                :on-click toggle)))

7 Nested Providers

Providers can be nested - inner providers override outer ones:

(vui-defcontext level 0)

(vui-defcomponent nested-example ()
  :render
  (level-provider 1
                  (vui-fragment
                   (vui-component 'show-level)  ; Shows 1
                   (vui-newline)
                   (level-provider 2
                                   (vui-component 'show-level)))))  ; Shows 2

(vui-defcomponent show-level ()
  :render
  (vui-text (format "Level: %d" (use-level))))

8 Multiple Contexts

You can define and use multiple contexts:

(vui-defcontext user nil)
(vui-defcontext theme 'light)
(vui-defcontext locale "en")

(vui-defcomponent app ()
  :state ((user '(:name "Alice"))
          (theme 'dark)
          (locale "fr"))
  :render
  (user-provider user
                 (theme-provider theme
                                 (locale-provider locale
                                                  (vui-component 'main-view)))))

(vui-defcomponent main-view ()
  :render
  (let ((user (use-user))
        (theme (use-theme))
        (locale (use-locale)))
    (vui-text (format "User: %s, Theme: %s, Locale: %s"
                      (plist-get user :name)
                      theme
                      locale))))

9 Default Values

When no provider is found, context returns the default value:

(vui-defcontext optional-data "default-value")

(vui-defcomponent uses-optional ()
  :render
  (let ((data (use-optional-data)))
    ;; Returns "default-value" if no provider above
    (vui-text (format "Data: %s" data))))

10 When to Use Context

10.1 Good Use Cases

  • Theme (dark/light mode)
  • Current user/authentication
  • Locale/internationalization
  • Feature flags
  • UI configuration (font size, density)

10.2 Avoid Using Context For

  • Data that changes frequently (use props or state instead)
  • Data only needed by one component (just use props)
  • Complex state management (consider other patterns)

11 Summary

APIPurpose
vui-defcontextDefine a new context
NAME-providerProvide context value to tree
use-NAMEConsume context value

12 Try It Yourself

Exercise: Create a notification system using context:

  1. Define a notifications context
  2. Create a notification-provider that manages a list of notifications
  3. Provide add-notification and clear-notifications functions
  4. Create a notification-list component that displays notifications
  5. Create an add-notification-button component
(vui-defcontext notifications nil)

(vui-defcomponent notification-app ()
  :state ((notifications '()))
  :render
  ;; Your implementation
  )

13 What’s Next?

  • Lifecycle - Component lifecycle hooks
  • Hooks - use-effect, use-ref, use-memo