React Getting Started
BlaC’s mental model is one line: state lives in a class (a Cubit), and useBloc connects a component to it, re-rendering only when the data that component actually reads changes. No providers to wire up, no reducers, no selectors required. For the full reasoning behind that design, see Mental Model.
This page gets you from install to a working counter and todo list. For the framework-agnostic introduction to Cubits, see Getting Started; this page focuses on the React binding.
Installation
Section titled “Installation”pnpm add @blac/core @blac/reactnpm install @blac/core @blac/reactRequires React 18+ and TypeScript is recommended.
Your first component
Section titled “Your first component”Two steps: define a Cubit that holds state and exposes methods to change it, then connect a component to it with useBloc. The snippet below is complete and copy-pasteable.
import { Cubit } from '@blac/core';import { useBloc } from '@blac/react';
class CounterCubit extends Cubit<{ count: number }> { constructor() { super({ count: 0 }); // initial state goes to super() }
// Arrow-function fields keep `this` bound, so you can pass them straight // to onClick without wrapping. (See "common mistakes" below.) increment = () => this.emit({ count: this.state.count + 1 }); decrement = () => this.update((s) => ({ count: s.count - 1 }));}
function Counter() { const [state, counter] = useBloc(CounterCubit);
return ( <div> <p>Count: {state.count}</p> <button onClick={counter.increment}>+</button> <button onClick={counter.decrement}>-</button> </div> );}useBloc returns a tuple [state, bloc]:
- state — the current state, tracked for re-renders. Reading
state.counthere subscribes this component tocountonly; if the state grows other fields, changing them won’t re-renderCounter. - bloc — the Cubit instance. Call its methods to drive state changes (
counter.increment()).
There is no <Provider> to add at the root and nothing to register. The first component to call useBloc(CounterCubit) creates the instance; the last one to unmount disposes it.
Three ways to update state
Section titled “Three ways to update state”A Cubit exposes three mutation methods. Most code uses emit; reach for the others when they read better:
| Method | Shape | Use when |
|---|---|---|
emit | emit(nextState) | You’re replacing the whole state object. |
update | update((prev) => nextState) | The next state derives from the previous one. |
patch | patch(partial) | You want to deep-merge a few fields and leave the rest. |
Where does the state live?
The instance is held in a global registry and reference-counted by useBloc. The “what just happened” details — acquire on mount, release on unmount, automatic disposal at zero refs — are covered in Instance Management, and the reasoning behind the design is in Mental Model.
Tracking modes at a glance
Section titled “Tracking modes at a glance”Auto-tracking is on by default and needs no configuration. The only knob is per call: pass a select function to useBloc to choose exactly what drives re-renders instead.
| Mode | How to enable | Re-renders when |
|---|---|---|
| Auto-tracking | Default (no select) | A state path you read during render changes |
| Manual select | select: (s) => [s.count] | A selected value changes (per-index Object.is) |
Instance modes at a glance
Section titled “Instance modes at a glance”By default all components calling useBloc(SameCubit) share one instance. You can scope instances by args values or per mount:
| Mode | How to enable | Behavior |
|---|---|---|
| Shared | Default (no args) | All components share one instance |
| Per-args (default hash) | { args: { id } } | Each distinct args value gets its own instance |
| Per-args (custom key) | { args: { id } } + static key = (a) => a.id | Only the keyed field forks instances; others ride along |
| Per-mount | { args: { _id: useId() } } + static key = (a) => a._id | Each component mount gets a private instance |
See Passing Inputs for the full identity model and precedence.
What’s next?
Section titled “What’s next?”Once the counter works, the natural next steps are reading state efficiently and giving blocs input:
- useBloc — Full hook reference, options, and identity rules
- Dependency Tracking — How smart re-renders work and their limits
- Passing Inputs —
args, per-mount instances, and identity keying
See also
Section titled “See also”- Mental Model — Why BlaC works the way it does
- Cubit — The state container:
emit,update,patch, lifecycle - Performance — Patterns and anti-patterns
- Getting Started — Framework-agnostic introduction