Skip to content

feat: add createExternalStore utility and ClockProvider#195

Open
gcutrini wants to merge 2 commits into
mainfrom
feature/external-store-clock-context
Open

feat: add createExternalStore utility and ClockProvider#195
gcutrini wants to merge 2 commits into
mainfrom
feature/external-store-clock-context

Conversation

@gcutrini
Copy link
Copy Markdown
Contributor

@gcutrini gcutrini commented Feb 5, 2026

Add a generic external store factory (createExternalStore) using useSyncExternalStore that lets components subscribe to frequently-updating data sources without unnecessary re-renders.

Includes a pre-built clock store (ClockProvider, useClock, useClockSelector) wrapping the existing Clock component. This enables projects to move clock state out of Redux, eliminating per-second re-renders of all connected components.

API

  • createExternalStore(name){ Provider, useValue, useSelector }
  • useValue() — re-renders on every emit
  • useSelector(compute, isEqual) — re-renders only when the computed result changes

Clock usage

import { ClockProvider, useClock, useClockSelector } from 'openstack-uicore-foundation/lib/components/clock-context';

<ClockProvider timezone={summit.time_zone_id}>
  <App />
</ClockProvider>

const nowUtc = useClock(); // re-renders every second
const allowedTickets = useClockSelector(compute, isEqual); // re-renders only on change

ref: https://app.clickup.com/t/86b8dm4da

Summary by CodeRabbit

  • New Features

    • Added a clock context with provider and hooks for managing server-synced time updates.
    • Introduced an external store pattern with selector support for efficient derived-state subscriptions.
    • Added React 16/17 compatibility via the use-sync-external-store shim.
  • Documentation

    • Added a React compatibility note to the README with upgrade guidance.
  • Tests

    • Comprehensive tests covering clock context and external store behaviors.

Review Change Stack

@gcutrini gcutrini requested a review from smarcet February 5, 2026 15:01
Add a generic external store factory (createExternalStore) using
useSyncExternalStore that allows components to subscribe to
frequently-updating data sources without causing unnecessary re-renders.

Includes a pre-built clock store (ClockProvider, useClock,
useClockSelector) that wraps the existing Clock component, enabling
projects to move clock state out of Redux and eliminate per-second
re-renders of all connected components.
@gcutrini gcutrini force-pushed the feature/external-store-clock-context branch from 8f3375c to cd28bef Compare May 18, 2026 18:38
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fbbd43bf-e5dc-4cb8-a949-44fa615a4f9c

📥 Commits

Reviewing files that changed from the base of the PR and between cd28bef and 37adf17.

📒 Files selected for processing (1)
  • src/components/__tests__/clock-context.test.js

📝 Walkthrough

Walkthrough

Adds a createExternalStore factory (Provider, useValue, useSelector) with React 16/17 shim, a ClockProvider wiring Clock into that store, tests for both modules, barrel exports, webpack entries, and README/dependency updates describing the shim and migration to React 18+.

Changes

External Store and Clock Context Implementation

Layer / File(s) Summary
External Store Implementation
src/utils/external-store.js
Core factory createExternalStore(name) returning { Provider, useValue, useSelector }. Provider holds latest value, manages listeners, and integrates with useSyncExternalStore. useValue subscribes to every emit; useSelector computes derived values and suppresses re-renders using an equality comparator.
External Store Test Suite
src/utils/__tests__/external-store.test.js
Jest suite covering Provider render-prop and emit, useValue behavior and re-render counts, useSelector memoization and custom equality, multi-subscriber behavior, independent store isolation, and error messages that include the store name.
Clock Context Implementation
src/components/clock-context.js
Pre-built clock context that calls createExternalStore('Clock'), provides ClockProvider which mounts Clock and forwards emit as onTick, and re-exports useClock and useClockSelector.
Clock Context Test Suite
src/components/__tests__/clock-context.test.js
Tests that mock Clock to capture onTick, assert ClockProvider mounts and supplies onTick, useClock and useClockSelector throw when used outside provider, update on ticks, and that selector skips renders when output unchanged.
Public API Exports and Build Configuration
src/components/index.js, webpack.common.js
Barrel exports ClockProvider, useClock, useClockSelector, and createExternalStore; webpack adds entries for components/clock-context and utils/external-store.
Dependencies and Documentation
package.json, readme.md
Adds use-sync-external-store@^1.6.0 to dependencies for React 16/17 compatibility; README documents shim usage and advises switching to React 18+ native import when upgrading.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A store that hums with listeners near,
Clock ticks travel, clean and clear.
Hooks subscribe and selectors hush,
Tests ensure no accidental rush.
A tiny shim helps older React hear.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main changes: introducing createExternalStore utility and ClockProvider component. It reflects the primary objectives of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/external-store-clock-context

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/__tests__/clock-context.test.js`:
- Around line 58-62: Two tests directly reassign console.error around rendering
ClockReader which can leak mocks if an exception occurs; replace those direct
assignments with jest.spyOn(console, 'error').mockImplementation(...) and ensure
you call mockRestore() in a finally block so the spy is always restored,
updating both the block that wraps render(<ClockReader />) (previously lines
58-62) and the similar block around lines 80-84; reference the ClockReader
render and use mockRestore() to guarantee cleanup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 322fb309-5b6c-4966-b31b-9e06d7494ddd

📥 Commits

Reviewing files that changed from the base of the PR and between e01d8de and cd28bef.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (8)
  • package.json
  • readme.md
  • src/components/__tests__/clock-context.test.js
  • src/components/clock-context.js
  • src/components/index.js
  • src/utils/__tests__/external-store.test.js
  • src/utils/external-store.js
  • webpack.common.js

Comment thread src/components/__tests__/clock-context.test.js Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant