Claude-skill-registry blac-development
Develop with BlaC state management library for React. Use when creating Cubits, using useBloc/useBlocActions hooks, managing state containers, or implementing inter-bloc communication patterns.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/blac-development" ~/.claude/skills/majiayu000-claude-skill-registry-blac-development && rm -rf "$T"
manifest:
skills/data/blac-development/SKILL.mdsource content
BlaC Development Skill
BlaC is a TypeScript state management library with React integration using proxy-based dependency tracking for optimal re-renders.
Installation
pnpm add @blac/core @blac/react
Core Concepts
Cubit - Simple State Container
Use Cubit for direct state mutations without events. Best for most use cases.
import { Cubit } from '@blac/core'; class CounterCubit extends Cubit<{ count: number }> { constructor() { super({ count: 0 }); // initial state (must be an object) } // IMPORTANT: Always use arrow functions for React compatibility increment = () => { this.emit({ count: this.state.count + 1 }); }; decrement = () => { this.emit({ count: this.state.count - 1 }); }; }
State Update Methods:
- Emit new state directlyemit(newState)
- Update with functionupdate(fn)(current) => next
- Shallow merge partial state (object state only)patch(partial)
Class Configuration with @blac()
Decorator
@blac()import { blac, Cubit } from '@blac/core'; // Isolated: Each component gets its own instance @blac({ isolated: true }) class FormBloc extends Cubit<FormState> {} // KeepAlive: Never auto-dispose when ref count reaches 0 @blac({ keepAlive: true }) class AuthBloc extends Cubit<AuthState> {} // Exclude from DevTools (prevents infinite loops) @blac({ excludeFromDevTools: true }) class InternalBloc extends Cubit<State> {} // Function syntax (no decorator support) const MyBloc = blac({ isolated: true })(class extends Cubit<State> {});
Note:
BlacOptions is a union type - only ONE option can be specified at a time.
React Integration
useBloc Hook - Automatic Dependency Tracking
import { useBloc } from '@blac/react'; function Counter() { const [state, cubit] = useBloc(CounterCubit); // Only re-renders when accessed properties change return ( <div> <p>Count: {state.count}</p> <button onClick={cubit.increment}>+</button> </div> ); }
Returns:
[state, blocInstance, componentRef]
useBloc Options
const [state, bloc] = useBloc(MyBloc, { props: { userId: '123' }, // Constructor arguments instanceId: 'main', // Custom instance ID for shared blocs dependencies: (state, bloc) => [state.count], // Manual dependency tracking autoTrack: false, // Disable automatic tracking disableGetterCache: false, // Disable getter value caching (advanced) onMount: (bloc) => bloc.fetchData(), // Lifecycle callbacks onUnmount: (bloc) => bloc.cleanup(), });
useBlocActions Hook - Actions Only (No State Subscription)
import { useBlocActions } from '@blac/react'; function ActionsOnly() { const bloc = useBlocActions(CounterBloc); // Never re-renders due to state changes return <button onClick={bloc.increment}>+</button>; }
Instance Management
Static Methods
| Method | Purpose | Ref Count |
|---|---|---|
| Get/create with ownership | Increments |
| Borrow existing (throws if missing) | No change |
| Borrow existing (returns error) | No change |
| Get/create for B2B communication | No change |
| Release reference | Decrements |
Bloc-to-Bloc Communication
In event handlers or methods (borrowing):
class UserBloc extends Cubit<UserState> { loadProfile = () => { // Borrow - no memory leak, no cleanup needed const analytics = AnalyticsCubit.get(); analytics.trackEvent('profile_loaded'); }; }
In getters (automatic tracking):
class CartCubit extends Cubit<CartState> { get totalWithShipping(): number { const shipping = ShippingCubit.get(); // Auto-tracked! return this.itemTotal + shipping.state.cost; } }
System Events
class MyBloc extends Cubit<State> { constructor() { super(initialState); this.onSystemEvent('stateChanged', ({ state, previousState }) => { console.log('State changed'); }); this.onSystemEvent('propsUpdated', ({ props, previousProps }) => { console.log('Props updated'); }); this.onSystemEvent('dispose', () => { console.log('Disposing - cleanup here'); }); } }
Best Practices
DO:
- Use arrow functions for methods (correct
binding in React)this - Keep state immutable (create new objects/arrays)
- Use
for simple field updates,patch()
for nested changesupdate() - Use
when only calling methods (no state reading)useBlocActions - Use getters for computed values (cached per render cycle)
- Use
instead of.get()
in bloc-to-bloc communication.resolve()
DON'T:
- Mutate state directly (
)this.state.todos.push(...) - Destructure entire state when you only need specific properties
- Use
in getters (causes memory leaks).resolve() - Use
for nested object updates (shallow merge only)patch()
Common Patterns
Form State (Isolated)
@blac({ isolated: true }) class FormBloc extends Cubit<FormState> { constructor() { super({ values: {}, errors: {}, isSubmitting: false }); } setField = (field: string, value: string) => { this.update(state => ({ ...state, values: { ...state.values, [field]: value }, errors: { ...state.errors, [field]: '' } })); }; }
Async Data Fetching
class UserBloc extends Cubit<DataState<User>> { constructor() { super({ data: null, isLoading: false, error: null }); } fetchUser = async (id: string) => { this.patch({ isLoading: true, error: null }); try { const data = await api.getUser(id); this.patch({ data, isLoading: false }); } catch (error) { this.patch({ error: error.message, isLoading: false }); } }; }
Shared Singleton Service
@blac({ keepAlive: true }) class AnalyticsService extends Cubit<AnalyticsState> { trackEvent = (name: string, data: Record<string, any>) => { // Other blocs can safely call: AnalyticsService.get().trackEvent(...) }; }
Troubleshooting
Component not re-rendering?
- Access state properties during render, not before
- Check you're using
, not just readinguseBlocbloc.state
Too many re-renders?
- Access only the properties you need
- Don't destructure entire state object
- Consider
for action-only componentsuseBlocActions
Shared state not working?
- Check if bloc is marked
@blac({ isolated: true }) - Verify same
if using custom IDsinstanceId
Performance Optimization
Optimal Property Access
// ✅ OPTIMAL: Access only what you render function UserCard() { const [user] = useBloc(UserBloc); return <h2>{user.name}</h2>; // Only tracks 'name' } // ❌ AVOID: Destructuring tracks everything const { name, email, bio } = user; // Re-renders on ANY change
Component Splitting
// ✅ Split into granular components function TodoApp() { return ( <> <TodoCount /> {/* Only re-renders on count change */} <TodoList /> {/* Only re-renders on todos change */} <TodoActions /> {/* Never re-renders (uses useBlocActions) */} </> ); } function TodoActions() { const cubit = useBlocActions(TodoCubit); // No state subscription return <button onClick={cubit.addTodo}>Add</button>; }
Performance Summary
| Pattern | Re-renders | Use When |
|---|---|---|
| Auto-tracking (default) | On tracked property change | Most cases |
| Never | Action-only components |
Manual | On dependency change | Known fixed dependencies |
| Getters | On computed value change | Derived/computed state |
Common Mistakes
- Destructuring state - Tracks all destructured properties
- Spreading props -
defeats tracking<Child {...state} /> - Using
in methods - Use.resolve()
for bloc-to-bloc calls.get() - Not using
- Creates unnecessary subscriptionsuseBlocActions
For complete API reference, see REFERENCE.md.